쇼핑몰 프로젝트

쇼핑몰 프로젝트 --페이지네이션, 유효성검사, 리뷰 정렬(화면구현)

개발공부중인지니 2024. 6. 28. 02:05

itemList, itemCategoryList, categoryGet, itemReviewGet페이지네이션 추가 

 

 

itemList페이지네이션 추가 

itemRepository

// 아이템 전체 총 개수 조회 (페이징 하기 위해)
public int countAll(String keyword) {
    String queryString = "select count(i) from Item i";
    if (keyword != null && !keyword.isEmpty()) {
        queryString += " where i.itemName like :keyword";
    }
    TypedQuery<Long> query = em.createQuery(queryString, Long.class);
    if (keyword != null && !keyword.isEmpty()) {
        query.setParameter("keyword", "%" + keyword + "%");
    }
    return query.getSingleResult().intValue();
}

 

 

itemService

//아이템 전체 페이징 개수 조회
public int countAll(String keyword) {
    return itemRepository.countAll(keyword);
}

 

 

itemController

//아이템 전체 조회
@GetMapping("/ypjs/item/get")
public String getAllItem(
                                 @RequestParam(value = "page",defaultValue = "0") int page,
                                 @RequestParam(value = "size",defaultValue = "3") int size,
                                 @RequestParam(value = "sortBy", defaultValue = "itemId") String sortBy,
                                 @RequestParam(value = "keyword", required = false) String keyword,
                                 Model model) {

    Pageable pageable = PageRequest.of(page, size);

    List<ItemListDto> items = itemService.findAllItem(keyword, pageable, sortBy);

    //총 페이지 수 계산
    int totalPages = Page.totalPages(itemService.countAll(keyword), size);

    model.addAttribute("items", items);
    model.addAttribute("sortBy", sortBy); // 정렬 옵션을 다시 모델에 추가
    model.addAttribute("keyword", keyword); //검색조건 유지
    model.addAttribute("page",page); //페이징
    model.addAttribute("size",size); //페이징
    model.addAttribute("totalPages", totalPages); //총 페이지 수


    return "item/itemList";


}

 

오늘 서비스, 레파지토리는 페이징 부분만 추가, 나머지 서비스, 레파지토리는 전에 적어논 글에 있음

 

 

item/itemList 페이지네이션 부분만 추가 됨

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">

<head th:replace="frame/header :: head">
    <title>Zay Shop - Product Listing Page</title>
</head>

<style>
    .fixed-size-img {
        width: 300px; /* 원하는 너비로 설정 */
        height: 300px; /* 원하는 높이로 설정 */
        object-fit: cover; /* 이미지를 크기에 맞게 조정 */
    }
</style>

<body>
<!--header-->
<header th:replace="frame/header :: header"></header>
<script src="/js/item/itemRatingsGet.js"></script>

<!-- Start Content -->
<div class="container py-5">
    <div class="row">


        <div class="container-fluid mt-3">
            <div class="row">
                <div class="col-md-12">
                    <ul class="list-inline shop-top-menu pb-3 pt-1">
                        <li class="list-inline-item">
                            <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/item/post}">상품 등록</a>
                        </li>
                        <li class="list-inline-item">
                            <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/category/post}">카테고리 등록</a>
                        </li>
                        <li class="list-inline-item">
                            <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/category/get}">카테고리 보기</a>
                        </li>

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



        <div class="col-lg-3">
            <h1 class="h2 pb-4">Categories</h1>
            <ul class="list-unstyled templatemo-accordion">
                <li class="pb-3">
                    <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#">
                        Women
                        <i class="fa fa-fw fa-chevron-circle-down mt-1"></i>
                    </a>
                    <ul class="collapse show list-unstyled pl-3">
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=3)}">Outer</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=4)}">Top</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=5)}">Bottom</a></li>
                    </ul>
                </li>
                <li class="pb-3">
                    <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#">
                        Man
                        <i class="pull-right fa fa-fw fa-chevron-circle-down mt-1"></i>
                    </a>
                    <ul id="collapseTwo" class="collapse list-unstyled pl-3">
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=6)}">Outer</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=7)}">Top</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=8)}">Bottom</a></li>
                    </ul>
                </li>

            </ul>
        </div>







        <div class="col-lg-9">
            <div class="row">
                <div class="col-md-6">
                    <ul class="list-inline shop-top-menu pb-3 pt-1">
                        <li class="list-inline-item">
                            <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/item/get}">All Items</a>
                        </li>

                    </ul>
                </div>




                <div class="col-lg-6">
                    <!-- 검색 폼 및 정렬 폼 통합 -->
                    <form th:action="@{/ypjs/item/get}" method="get" class="input-group">
                        <input type="text" id="keyword" name="keyword" class="form-control"
                               placeholder="Search by item name" th:value="${keyword}">
                        <div class="input-group-append">
                            <button type="submit" class="btn btn-link text-dark">
                                <i class="fa fa-search"></i>
                            </button>
                        </div>
                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                        <div class="col-auto">
                            <select class="form-control" name="sortBy" id="sortBy" onchange="this.form.submit()">
                                <option value="itemId" th:selected="${sortBy == 'itemId'}">최신순</option>
                                <option value="itemRatings" th:selected="${sortBy == 'itemRatings'}">별점 높은 순</option>
                                <option value="likeCount" th:selected="${sortBy == 'likeCount'}">찜많은순</option>
                            </select>
                        </div>
                    </form>
                </div>





<!--                반별 추가-->

<!--                <div class="container-fluid mt-3">-->
<!--                    <div class="row">-->
<!--                        &lt;!&ndash; items 리스트의 각 항목을 반복 &ndash;&gt;-->
<!--                        <div th:each="item : ${items}" class="col-md-4">-->
<!--                            <div class="card mb-4 product-wap rounded-0">-->
<!--                                &lt;!&ndash; 제품 이미지 &ndash;&gt;-->
<!--                                <div class="card rounded-0">-->
<!--                                    <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}">-->
<!--                                        <img class="card-img rounded-0 img-fluid fixed-size-img" th:src="${item.itemFilePath}" alt="product-item">-->
<!--                                        &lt;!&ndash; 필요한 경우 오버레이나 추가 요소를 여기에 추가할 수 있습니다 &ndash;&gt;-->
<!--                                        <div class="card-img-overlay rounded-0 product-overlay d-flex align-items-center justify-content-center">-->
<!--                                            &lt;!&ndash; 추가적인 콘텐츠가 필요하다면 여기에 추가할 수 있습니다 &ndash;&gt;-->
<!--                                            <ul class="list-unstyled">-->
<!--                                                &lt;!&ndash; 리스트의 다른 요소들 &ndash;&gt;-->
<!--                                            </ul>-->
<!--                                        </div>-->
<!--                                    </a>-->
<!--                                </div>-->
<!--                                &lt;!&ndash; 제품 정보 &ndash;&gt;-->
<!--                                <div class="card-body">-->
<!--                                    &lt;!&ndash; 제품 링크 &ndash;&gt;-->
<!--                                    <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a>-->

<!--                                    &lt;!&ndash; 별점 및 평점 표시 &ndash;&gt;-->
<!--                                    <div class="d-flex justify-content-center mb-1 align-items-center">-->
<!--                                        <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${item.itemRatings}">-->
<!--                                            &lt;!&ndash; 별 아이콘은 JavaScript로 생성됩니다 &ndash;&gt;-->
<!--                                        </ul>&nbsp;&nbsp;&nbsp;&nbsp;-->
<!--                                        <span id="itemRatings" class="font-weight-bold ml-2" th:text="${item.itemRatings}"></span>-->
<!--                                    </div>-->

<!--                                    &lt;!&ndash; 가격 표시 &ndash;&gt;-->
<!--                                    <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span>-->
<!--                                    <span class="font-weight-bold">원</span>-->
<!--                                </div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                </div>-->

<!--                <script>-->
<!--                    document.addEventListener("DOMContentLoaded", function() {-->
<!--                        var starRatingContainers = document.querySelectorAll("#starRating");-->

<!--                        starRatingContainers.forEach(function(starRatingContainer) {-->
<!--                            var rating = parseFloat(starRatingContainer.getAttribute("data-rating"));-->
<!--                            starRatingContainer.innerHTML = generateStarRating(rating);-->
<!--                        });-->
<!--                    });-->

<!--                    function generateStarRating(rating) {-->
<!--                        var starsHTML = '';-->
<!--                        var fullStars = Math.floor(rating);  // 정수 부분의 별 개수-->
<!--                        var halfStar = (rating % 1 >= 0.1 && rating % 1 <= 0.9) ? 1 : 0; // 반 별 조건-->

<!--                        // 정수 부분의 별 추가-->
<!--                        for (var i = 0; i < fullStars; i++) {-->
<!--                            starsHTML += '<i class="text-warning fa fa-star"></i>';-->
<!--                        }-->

<!--                        // 반 별 추가-->
<!--                        if (halfStar) {-->
<!--                            starsHTML += '<i class="text-warning fa fa-star-half-alt"></i>';-->
<!--                        }-->

<!--                        // 남은 빈 별 추가-->
<!--                        var emptyStars = 5 - fullStars - halfStar;-->
<!--                        for (var i = 0; i < emptyStars; i++) {-->
<!--                            starsHTML += '<i class="text-muted fa fa-star"></i>';-->
<!--                        }-->

<!--                        return starsHTML;-->
<!--                    }-->
<!--                </script>-->





<!--                꽉찬 별만-->
                <div class="container-fluid mt-3">
                    <div class="row">
                        <!-- items 리스트의 각 항목을 반복 -->
                        <div th:each="item : ${items}" class="col-md-4">
                            <div class="card mb-4 product-wap rounded-0">
                                <!-- 제품 이미지 -->
                                <div class="card rounded-0">
                                    <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}">
                                        <img class="card-img rounded-0 img-fluid fixed-size-img" th:src="${item.itemFilePath}" alt="product-item">
                                        <!-- 필요한 경우 오버레이나 추가 요소를 여기에 추가할 수 있습니다 -->
                                        <div class="card-img-overlay rounded-0 product-overlay d-flex align-items-center justify-content-center">
                                            <!-- 추가적인 콘텐츠가 필요하다면 여기에 추가할 수 있습니다 -->
                                            <ul class="list-unstyled">
                                                <!-- 리스트의 다른 요소들 -->
                                            </ul>
                                        </div>
                                    </a>
                                </div>
                                <!-- 제품 정보 -->
                                <div class="card-body">
                                    <!-- 제품 링크 -->
                                    <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a>

                                    <!-- 별점 및 평점 표시 -->
                                    <div class="d-flex justify-content-center mb-1 align-items-center">
                                        <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${item.itemRatings}">
                                            <!-- 별 아이콘은 JavaScript로 생성됩니다 -->
                                        </ul> &nbsp;&nbsp;&nbsp;&nbsp;
                                        <span id="itemRatings" class="font-weight-bold ml-2" th:text="${item.itemRatings}"></span>
                                    </div>

                                    <!-- 가격 표시 -->
                                    <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span>
                                    <span class="font-weight-bold">원</span>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>



            </div>
        </div>

    </div>
</div>



<!-- 페이지네이션 시작-->
<nav th:unless="${#lists.isEmpty(items)}" class="mt-4">
    <ul class="pagination justify-content-center">
        <li class="page-item" th:classappend="${page == 0} ? 'disabled'">
            <a class="page-link" th:href="@{'/ypjs/item/get?page=' + ${page - 1} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">이전</a>
        </li>
        <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'">
            <a class="page-link" th:href="@{'/ypjs/item/get?page=' + ${i} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}" th:text="${i + 1}">1</a>
        </li>
        <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'">
            <a class="page-link" th:href="@{'/ypjs/item/get?page=' + ${page + 1} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">다음</a>
        </li>
    </ul>
</nav>

<!--페이지네이션 끝-->



<!-- End Content -->

<!-- Start Brands -->
<section class="bg-light py-5">
    <div class="container my-4">
        <div class="row text-center py-3">
            <div class="col-lg-6 m-auto">
                <h1 class="h1">Our Brands</h1>
                <p>
                    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
                    Lorem ipsum dolor sit amet.
                </p>
            </div>
            <div class="col-lg-9 m-auto tempaltemo-carousel">
                <div class="row d-flex flex-row">
                    <!--Controls-->
                    <div class="col-1 align-self-center">
                        <a class="h1" href="#multi-item-example" role="button" data-bs-slide="prev">
                            <i class="text-light fas fa-chevron-left"></i>
                        </a>
                    </div>
                    <!--End Controls-->

                    <!--Carousel Wrapper-->
                    <div class="col">
                        <div class="carousel slide carousel-multi-item pt-2 pt-md-0" id="multi-item-example" data-bs-ride="carousel">
                            <!--Slides-->
                            <div class="carousel-inner product-links-wap" role="listbox">

                                <!--First slide-->
                                <div class="carousel-item active">
                                    <div class="row">
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a>
                                        </div>
                                    </div>
                                </div>
                                <!--End First slide-->

                                <!--Second slide-->
                                <div class="carousel-item">
                                    <div class="row">
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a>
                                        </div>
                                    </div>
                                </div>
                                <!--End Second slide-->

                                <!--Third slide-->
                                <div class="carousel-item">
                                    <div class="row">
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a>
                                        </div>
                                    </div>
                                </div>
                                <!--End Third slide-->

                            </div>
                            <!--End Slides-->
                        </div>
                    </div>
                    <!--End Carousel Wrapper-->

                    <!--Controls-->
                    <div class="col-1 align-self-center">
                        <a class="h1" href="#multi-item-example" role="button" data-bs-slide="next">
                            <i class="text-light fas fa-chevron-right"></i>
                        </a>
                    </div>
                    <!--End Controls-->
                </div>
            </div>
        </div>
    </div>
</section>
<!--End Brands-->


<footer th:replace="frame/footer :: footer"></footer>

</body>

</html>

 

 

 

 

 

 

itemCategoryList 페이지네이션 

 

itemRepository

//카테고리 별 아이템 총 개수 조회(페이징)
public int countAllCategoryItem(String keyword, Long categoryId) {
    // 기본 쿼리 설정
    String queryString = "select count(distinct i) from Item i join i.category c where i.category.categoryId = :categoryId";

    // 검색 조건 추가
    if (keyword != null && !keyword.isEmpty()) {
        queryString += " and i.itemName like :keyword";
    }

    // 쿼리 생성
    TypedQuery<Long> query = em.createQuery(queryString, Long.class);
    query.setParameter("categoryId", categoryId);

    // 검색 키워드 파라미터 설정
    if (keyword != null && !keyword.isEmpty()) {
        query.setParameter("keyword", "%" + keyword + "%");
    }

    // 결과 반환
    return query.getSingleResult().intValue();
}

 

 

itemService

//카테고리 별 아이템 페이징 개수 조회
public int countAllCategoryItem(String keyword, Long categoryId) {
    return itemRepository.countAllCategoryItem(keyword,categoryId);
}

 

 

 

itemController

//카테고리당 아이템 조회(정렬,검색,페이징(페이징받아서 페이징))
@GetMapping("/ypjs/categoryItem/get/{categoryId}")
public String getAllCategoryItem(@PathVariable("categoryId") Long categoryId,
                                 @RequestParam(value = "page",defaultValue = "0") int page,
                                 @RequestParam(value = "size",defaultValue = "3") int size,
                                 @RequestParam(value = "sortBy", defaultValue = "itemId") String sortBy,
                                 @RequestParam(value = "keyword", required = false) String keyword,
                         Model model) {

    Pageable pageable = PageRequest.of(page, size);

    Category category = categoryService.findOneCategory(categoryId);

    List<ItemListDto> items = itemService.finaAllItemPagingSortBy(categoryId, keyword, pageable, sortBy);


    //총 페이지 수 계산
    int totalPages = Page.totalPages(itemService.countAllCategoryItem(keyword, categoryId), size);

    model.addAttribute("items",items);
    model.addAttribute("category", category);
    model.addAttribute("sortBy", sortBy); // 정렬 옵션을 다시 모델에 추가
    model.addAttribute("keyword", keyword); //검색조건 유지
    model.addAttribute("page",page); //페이징
    model.addAttribute("size",size); //페이징
    model.addAttribute("totalPages", totalPages); //총 페이지 수





    return "item/itemCategoryList";


}

 

 

item/itemCategoryList

페이지네이션 부분만 추가

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">

<head th:replace="frame/header :: head">
    <title>Zay Shop - Product Listing Page</title>
</head>

<style>
    .fixed-size-img {
        width: 300px; /* 원하는 너비로 설정 */
        height: 300px; /* 원하는 높이로 설정 */
        object-fit: cover; /* 이미지를 크기에 맞게 조정 */
    }
</style>

<body>
<!--header-->
<header th:replace="frame/header :: header"></header>
<script src="/js/item/itemRatingsGet.js"></script>

<!-- Start Content -->
<div class="container py-5">
    <div class="row">

        <div class="col-lg-3">
            <h1 class="h2 pb-4">Categories</h1>
            <ul class="list-unstyled templatemo-accordion">
                <li class="pb-3">
                    <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#">
                        Women
                        <i class="fa fa-fw fa-chevron-circle-down mt-1"></i>
                    </a>
                    <ul class="collapse show list-unstyled pl-3">
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=3)}">Outer</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=4)}">Top</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=5)}">Bottom</a></li>
                    </ul>
                </li>
                <li class="pb-3">
                    <a class="collapsed d-flex justify-content-between h3 text-decoration-none" href="#">
                        Man
                        <i class="pull-right fa fa-fw fa-chevron-circle-down mt-1"></i>
                    </a>
                    <ul id="collapseTwo" class="collapse list-unstyled pl-3">
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=6)}">Outer</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=7)}">Top</a></li>
                        <li><a class="text-decoration-none" th:href="@{/ypjs/categoryItem/get/{categoryId}(categoryId=8)}">Bottom</a></li>
                    </ul>
                </li>

            </ul>
        </div>

        <div class="col-lg-9">
            <div class="row">
                <div class="col-md-6">
                    <ul class="list-inline shop-top-menu pb-3 pt-1">
                        <li class="list-inline-item">
                            <a class="h3 text-dark text-decoration-none mr-3" th:href="@{/ypjs/item/get}">All Items</a>
                        </li>

                    </ul>
                </div>




                <div class="col-lg-6">
                    <!-- 검색 폼 및 정렬 폼 통합 -->
                    <form th:action="@{/ypjs/categoryItem/get/{categoryId}(categoryId=${category.categoryId})}" method="get" class="input-group">
                        <input type="text" id="keyword" name="keyword" class="form-control"
                               placeholder="Search by item name" th:value="${keyword}">
                        <div class="input-group-append">
                            <button type="submit" class="btn btn-link text-dark">
                                <i class="fa fa-search"></i>
                            </button>
                        </div>
                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                        <div class="col-auto">
                            <select class="form-control" name="sortBy" id="sortBy" onchange="this.form.submit()">
                                <option value="itemId" th:selected="${sortBy == 'itemId'}">최신순</option>
                                <option value="itemRatings" th:selected="${sortBy == 'itemRatings'}">별점 높은 순</option>
                                <option value="likeCount" th:selected="${sortBy == 'likeCount'}">찜많은순</option>
                            </select>
                        </div>
                    </form>
                </div>






                <div class="container-fluid mt-3">
                <div class="row">
                    <!-- items 리스트의 각 항목을 반복 -->
                    <div th:each="item : ${items}" class="col-md-4">
                        <div class="card mb-4 product-wap rounded-0">
                            <!-- 제품 이미지 -->
                            <div class="card rounded-0">
                                <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}">
                                    <img class="card-img rounded-0 img-fluid fixed-size-img" th:src="${item.itemFilePath}" alt="product-item">
                                    <!-- 필요한 경우 오버레이나 추가 요소를 여기에 추가할 수 있습니다 -->
                                    <div class="card-img-overlay rounded-0 product-overlay d-flex align-items-center justify-content-center">
                                        <!-- 추가적인 콘텐츠가 필요하다면 여기에 추가할 수 있습니다 -->
                                        <ul class="list-unstyled">
                                            <!-- 리스트의 다른 요소들 -->
                                        </ul>
                                    </div>
                                </a>
                            </div>
                            <!-- 제품 정보 -->
                            <div class="card-body">
                                <!-- 제품 링크 -->
                                <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a>

                                <!-- 별점 및 평점 표시 -->
                                <div class="d-flex justify-content-center mb-1 align-items-center">
                                    <ul class="list-unstyled d-flex mb-0" id="starRating" th:attr="data-rating=${item.itemRatings}">
                                        <!-- 별 아이콘은 JavaScript로 생성됩니다 -->
                                    </ul> &nbsp;&nbsp;&nbsp;&nbsp;
                                    <span id="itemRatings" class="font-weight-bold ml-2" th:text="${item.itemRatings}"></span>
                                </div>

                                <!-- 가격 표시 -->
                                <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span>
                                <span class="font-weight-bold">원</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>


            </div>
    </div>

</div>
</div>

<!-- 페이지네이션 시작-->
<nav th:unless="${#lists.isEmpty(items)}" class="mt-4">
    <ul class="pagination justify-content-center">
        <li class="page-item" th:classappend="${page == 0} ? 'disabled'">
            <a class="page-link" th:href="@{'/ypjs/categoryItem/get/' + ${category.categoryId} + '?page=' + (${page - 1}) + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">이전</a>
        </li>
        <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'">
            <a class="page-link" th:href="@{'/ypjs/categoryItem/get/' + ${category.categoryId} + '?page=' + ${i} + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}" th:text="${i + 1}">1</a>
        </li>
        <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'">
            <a class="page-link" th:href="@{'/ypjs/categoryItem/get/' + ${category.categoryId} + '?page=' + (${page + 1}) + '&size=' + ${size} + '&sortBy=' + ${sortBy} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">다음</a>
        </li>
    </ul>
</nav>
<!-- 페이지네이션 끝-->



<!-- End Content -->

<!-- Start Brands -->
<section class="bg-light py-5">
    <div class="container my-4">
        <div class="row text-center py-3">
            <div class="col-lg-6 m-auto">
                <h1 class="h1">Our Brands</h1>
                <p>
                    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
                    Lorem ipsum dolor sit amet.
                </p>
            </div>
            <div class="col-lg-9 m-auto tempaltemo-carousel">
                <div class="row d-flex flex-row">
                    <!--Controls-->
                    <div class="col-1 align-self-center">
                        <a class="h1" href="#multi-item-example" role="button" data-bs-slide="prev">
                            <i class="text-light fas fa-chevron-left"></i>
                        </a>
                    </div>
                    <!--End Controls-->

                    <!--Carousel Wrapper-->
                    <div class="col">
                        <div class="carousel slide carousel-multi-item pt-2 pt-md-0" id="multi-item-example" data-bs-ride="carousel">
                            <!--Slides-->
                            <div class="carousel-inner product-links-wap" role="listbox">

                                <!--First slide-->
                                <div class="carousel-item active">
                                    <div class="row">
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a>
                                        </div>
                                    </div>
                                </div>
                                <!--End First slide-->

                                <!--Second slide-->
                                <div class="carousel-item">
                                    <div class="row">
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a>
                                        </div>
                                    </div>
                                </div>
                                <!--End Second slide-->

                                <!--Third slide-->
                                <div class="carousel-item">
                                    <div class="row">
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_01.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_02.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_03.png" alt="Brand Logo"></a>
                                        </div>
                                        <div class="col-3 p-md-5">
                                            <a href="#"><img class="img-fluid brand-img" src="/img/brand_04.png" alt="Brand Logo"></a>
                                        </div>
                                    </div>
                                </div>
                                <!--End Third slide-->

                            </div>
                            <!--End Slides-->
                        </div>
                    </div>
                    <!--End Carousel Wrapper-->

                    <!--Controls-->
                    <div class="col-1 align-self-center">
                        <a class="h1" href="#multi-item-example" role="button" data-bs-slide="next">
                            <i class="text-light fas fa-chevron-right"></i>
                        </a>
                    </div>
                    <!--End Controls-->
                </div>
            </div>
        </div>
    </div>
</section>
<!--End Brands-->


<footer th:replace="frame/footer :: footer"></footer>

</body>

</html>

 

사진은 itemList와 동일

 

 

 

categoryList부분 검색, 페이징 없앰

 

categoryRepository

//category전체 조회
public List<Category> findAll() {
    return em.createQuery(
                    "select distinct c from Category c", Category.class)
            .getResultList();
}

 

 

categoryService

public List<CategoryListDto> findAllCategory() {
    
    List<Category> categories = categoryRepository.findAll();


    List<CategoryListDto> result = categories.stream()
            .map(c -> new CategoryListDto(c))
            .collect(Collectors.toList());

    return result;
}

 

 

 

categoryController

//카테고리 리스트 보기
@GetMapping("/ypjs/category/get")
public String getCategoryList(Model model) {
    
    List<CategoryListDto> categories = categoryService.findAllCategory();

    model.addAttribute("categories", categories);

    return "category/categoryList";
}

 

 

화면은 전에 올린 글에 있음

 

 

 

categoryGet에 검색, 페이지네이션 추가

 

 

itemRepository, service

위에 카테고리 별 아이템 조회 그대로 씀

 

 

categoryController

//category 1개 조회
@GetMapping("/ypjs/category/get/{categoryId}")
public String getOneCategory (@PathVariable("categoryId") Long categoryId,
                               Model model,
                              @RequestParam(value = "page", defaultValue = "0") int page,
                              @RequestParam(value = "size", defaultValue = "3") int size,
                              @RequestParam(value = "keyword", required = false) String keyword) {


    Pageable pageable = PageRequest.of(page, size);

    Category findCategory = categoryService.findOneCategory(categoryId);

    List<ItemListDto> items = itemService.findAllCategoryItem(categoryId, pageable, keyword);

    //총 페이지 수 계산
    int totalPages = Page.totalPages(itemService.countAllCategoryItem(keyword, categoryId), size);


    model.addAttribute("category", findCategory);
    model.addAttribute("items",items);
    model.addAttribute("keyword", keyword); //검색조건 유지
    model.addAttribute("page",page); //페이징
    model.addAttribute("size",size); //페이징
    model.addAttribute("totalPages", totalPages); //총 페이지 수


    return "category/categoryGet";


}

 

 

category/categoryGet 검색, 페이지네이션, 아이템 이름에 링크 추가 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">

<head th:replace="frame/header :: head">
    <title>Zay Shop - Product Listing Page</title>
</head>

<body>
<!--header-->
<header th:replace="frame/header :: header"></header>

<!-- Start Content -->
<div class="container py-5">
    카테고리 번호 당 아이템 리스트
</div>
<!-- End Content -->


<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<!-- JS 파일 포함 -->
<script src="/js/jquery-1.11.0.min.js"></script>
<script src="/js/summernote-lite.js"></script>
<script src="/js/summernote-ko-KR.js"></script>
<script src="/js/item/category.js"></script>
<script src="/js/item/item.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.18/summernote.min.js"></script>

<!-- Start Contact -->
<div class="container py-5">
    <div class="row py-5">

        <!-- 폼 요소들 -->
        <div class="row">

            <style>
                .category-box {
                    border: 1px solid #ccc;
                    padding: 10px;
                    margin-bottom: 10px;
                    max-width: 1000px; /* 최대 너비 설정 */
                    margin-left: auto;
                    margin-right: auto;
                }

                .category-label {
                    font-weight: bold;
                }

                .item-box {
                    border: 1px solid #ccc;
                    padding: 10px;
                    margin-bottom: 10px;
                    max-width: 1000px; /* 최대 너비 설정 */
                    margin-left: auto;
                    margin-right: auto;
                }

                .item-info {
                    /* Optional: Add additional styles for item information */
                }

                .category-label {
                    font-weight: bold;
                }

                 .h3-indent {
                    margin-left: 100px; /* 왼쪽 여백을 줌으로써 텍스트를 오른쪽으로 이동 */
                }
            </style>


            <div class="category-box">
                <div>
                    <span class="category-label">카테고리 번호:</span>
                    &nbsp;&nbsp;&nbsp;&nbsp; <span>:</span>&nbsp;&nbsp;&nbsp;&nbsp;
                    <span th:text="${category.categoryId}"></span>
                </div>
                <div>
                    <span class="category-label">카테고리 부모 번호:</span>
                    &nbsp;&nbsp;&nbsp;&nbsp; <span>:</span>&nbsp;&nbsp;&nbsp;&nbsp;
                    <span th:text="${category.categoryParent.categoryId}"></span>
                </div>
                <div>
                    <span class="category-label">카테고리 이름:</span>
                    &nbsp;&nbsp;&nbsp;&nbsp; <span>:</span>&nbsp;&nbsp;&nbsp;&nbsp;
                    <span th:text="${category.categoryName}"></span>
                </div>

                <br>

                <div class="container">
                    <div class="row justify-content-end">
                        <div class="col-auto">
                            <a th:href="@{/ypjs/category/update/{categoryId}(categoryId=${category.categoryId})}" class="btn btn-warning">수정하기</a>
                        </div>
                        <input type="hidden" id="categoryId" th:value="${category.categoryId}">
                        <div class="col-auto">
                            <button type="button" id="btn-categoryDelete" class="btn btn-danger">삭제하기</button>
                        </div>
                    </div>
                </div>
            </div>


            <div class="row">
                <div class="col-lg-9">
                    <div class="row justify-content-start">
                        <div class="col-md-6 offset-md-9">
                            <br><br>
                            <!-- 검색 폼 -->
                            <form th:action="@{/ypjs/category/get/{categoryId}(categoryId=${category.categoryId})}" method="get" class="form-inline w-100">
                                <div class="input-group mb-3">
                                    <input type="text" id="keyword" name="keyword" class="form-control" aria-label="Recipient's username" aria-describedby="button-addon2"
                                           placeholder="Search by item name" th:value="${keyword}">
                                    <button type="submit" class="btn btn-link text-dark mb-2 ml-2" id="button-addon2">
                                        <i class="fa fa-search"></i>
                                    </button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>

            <div th:if="${not #lists.isEmpty(items)}">
                <br><br><br>
                <h3 class="h3-indent">아이템 목록</h3>
                <br>
                <div th:each="item : ${items}" class="item-box">
                    <div class="item-info">
                        <div>
                            <span class="category-label">아이템 이름:</span>
                            &nbsp;&nbsp;&nbsp;&nbsp; <span>:</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            <a th:href="@{/ypjs/item/get/{itemId}(itemId=${item.itemId})}" id="itemName" class="font-weight-bold" th:text="${item.itemName}"></a>

                        </div>
                        <div>
                            <span class="category-label">아이템 내용:</span>
                            &nbsp;&nbsp;&nbsp;&nbsp; <span>:</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            <span th:utext="${item.itemContent}"></span>
                        </div>
                        <div>
                            <span class="category-label">아이템 가격:</span>
                            &nbsp;&nbsp;&nbsp;&nbsp; <span>:</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            <span th:text="${item.itemPrice}"></span>
                        </div>
                        <div>
                            <span class="category-label">아이템 수량:</span>
                            &nbsp;&nbsp;&nbsp;&nbsp; <span>:</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            <span th:text="${item.itemStock}"></span>
                        </div>
                        <!-- 필요한 다른 아이템 정보들을 추가 -->
                        <div class="container">
                            <div class="row justify-content-end">
                                <div class="col-auto">
                                    <a th:href="@{/ypjs/item/update/{itemId}(itemId=${item.itemId})}"
                                       class="btn btn-warning"
                                       style="padding: 2px 10px; font-size: 12px; width: 90px; height: 40px; text-align: center; display: inline-block; line-height: 40px;">
                                        수정하기
                                    </a>
                                </div>
                                &nbsp;
                                <button type="button"
                                        class="btn btn-danger btn-sm btn-delete-item"
                                        th:attr="data-itemId=${item.itemId}"
                                        style="padding: 2px 10px; font-size: 16px; width: 90px; height: 40px;">
                                    삭제하기
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>


<!-- 페이지네이션 시작-->
<nav th:unless="${#lists.isEmpty(items)}" class="mt-4">
    <ul class="pagination justify-content-center">
        <li class="page-item" th:classappend="${page == 0} ? 'disabled'">
            <a class="page-link" th:href="@{'/ypjs/category/get/' + ${category.categoryId} + '?page=' + (${page - 1}) + '&size=' + ${size} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">이전</a>
        </li>
        <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'">
            <a class="page-link" th:href="@{'/ypjs/category/get/' + ${category.categoryId} + '?page=' + ${i} + '&size=' + ${size} + '&keyword=' + ${#strings.defaultString(keyword, '')}}" th:text="${i + 1}">1</a>
        </li>
        <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'">
            <a class="page-link" th:href="@{'/ypjs/category/get/' + ${category.categoryId} + '?page=' + (${page + 1}) + '&size=' + ${size} + '&keyword=' + ${#strings.defaultString(keyword, '')}}">다음</a>
        </li>
    </ul>
</nav>
<!-- 페이지네이션 끝-->





<!-- End Contact -->

<footer th:replace="frame/footer :: footer"></footer>

</body>

</html>

 

 

 

검색

 

 

 

페이징

 

 

 

itemReview페이징

 

 

itemReviewRepository

//아이템리뷰 총 개수
public int countAllItemReview(Long itemId){
   return em.createQuery("select count(distinct ir) from ItemReview ir join ir.item i where i.itemId = :itemId", Long.class)
            .setParameter("itemId", itemId)
            .getSingleResult()
            .intValue();

}

 

 

itemReviewService


//아이템리뷰 페이징 개수 조회
public int countAllItemReview(Long itemId){
    return itemReviewRepository.countAllItemReview(itemId);
}

 

 

itmReviewController

//아이템 당 리뷰조회
@GetMapping("/ypjs/itemReview/get/{itemId}")
public String getAllItemReview(@PathVariable(name = "itemId") Long itemId, Model model,
                               @RequestParam(value = "page",defaultValue = "0") int page,
                               @RequestParam(value = "size",defaultValue = "10") int size) {

    Pageable pageable = PageRequest.of(page, size);

    itemService.findOneItem(itemId);

    List<ItemReviewListDto> itemReviews = itemReviewService.findAllItemReview(itemId, pageable);

    //총 페이지 수 계산
    int totalPages = Page.totalPages(itemReviewService.countAllItemReview(itemId), size);

    model.addAttribute("itemReviews", itemReviews);
    model.addAttribute("page",page); //페이징
    model.addAttribute("size",size); //페이징
    model.addAttribute("totalPages", totalPages); //총 페이지 수


    return "itemreview/itemReviewGet";
}

 

 

itemreview/itemReviewGet 페이지네이션 추가

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">

<head th:replace="frame/header :: head">
    <title>Zay Shop - Product Listing Page</title>
</head>

<script src="/js/item/itemRatingsGet.js"></script>

<body>
<!--header-->
<header th:replace="frame/header :: header"></header>

<style>
    .category-box {
        border: 1px solid #ccc;
        padding: 10px;
        margin-bottom: 10px;
    }

    .category-label {
        font-weight: bold;
    }

    .item-box {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}

.item-info {
/* Optional: Add additional styles for item information */
}

.category-label {
font-weight: bold;
}


     #reviewContent img {
       display: block; /* 이미지가 한 줄에 나타나도록 설정 */
        margin: auto; /* 가운데 정렬 */
        max-width: 100%; /* 최대 너비 설정 */
        height: auto; /* 높이 자동으로 조정 */
        margin-bottom: 10px; /* 이미지 아래 여백 추가 */
   }

</style>


<!-- Start Content -->
<div class="container py-5">
    아이템 리뷰
</div>
<!-- End Content -->


<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>


<!-- JS 파일 포함 -->
<script src="/js/jquery-1.11.0.min.js"></script>
<script src="/js/summernote-lite.js"></script>
<script src="/js/summernote-ko-KR.js"></script>
<script src="/js/item/itemReview.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.18/summernote.min.js"></script>


<!-- Start Contact -->
<div class="container py-5">
    <div class="row py-5">
        <form class="col-md-9 m-auto" method="post">
            <!-- 폼 요소들 -->
            <div class="row">


                <div th:if="${not #lists.isEmpty(itemReviews)}">
                    <br><br><br>
                    <h3>리뷰 목록</h3>
                    <br>
                    <div th:each="itemReview : ${itemReviews}" class="item-box">
                        <div class="item-info" style="text-align: right;">
                            <div>
                                <span style="font-weight: bold;">작성일 : </span>
                                <span id="itemReviewCreateDate"
                                      th:text="${#temporals.format(itemReview.itemReviewCreateDate, 'yyyy-MM-dd')}"></span>
                            </div>
                        </div>


                        <div class="item-info">
                            <div>
                                <span class="category-label">제목&nbsp;&nbsp;:&nbsp;&nbsp;</span>
                                <span th:text="${itemReview.itemReviewName}"></span>
                            </div>

                            <!-- 별점 및 평점 표시 -->
                            <div class="d-flex justify-content-left mb-1 align-items-left">
                                <span class="category-label">별점&nbsp;&nbsp;:&nbsp;&nbsp;</span>
                                <ul class="list-unstyled d-flex mb-0" id="starRating"
                                    th:attr="data-rating=${itemReview.itemScore}">
                                    <!-- 별 아이콘은 JavaScript로 생성됩니다 -->
                                </ul>

                            </div>


                            <div>
                                <span class="category-label">리뷰 내용&nbsp;&nbsp;:&nbsp;&nbsp;</span>
                                <div style="text-align: left;">
                                    <!-- 썸머노트에서 입력한 리뷰 내용 -->
                                    <div id="reviewContent" th:utext="${itemReview.itemReviewContent}">
                                        <!-- 썸머노트에서 삽입된 이미지의 스타일 -->
                                    </div>
                                </div>
                            </div>


                            <input type="hidden" id="itemId" th:value="${itemReview.itemId}">
                            <!-- 수정 및 삭제 버튼 -->
                            <div class="container">
                                <div class="row justify-content-end">
                                    <div class="col-auto">
                                        <a th:href="@{/ypjs/itemReview/update/{itemReviewId}(itemReviewId=${itemReview.itemReviewId})}"
                                           class="btn btn-warning">수정하기</a>
                                    </div>
                                    <div class="col-auto">

                                        <button type="button"
                                                class="btn btn-danger btn-sm btn-delete-itemReview"
                                                th:data-itemReviewId="${itemReview.itemReviewId}"
                                                style="padding: 2px 10px; font-size: 16px; width: 90px; height: 40px;">
                                            삭제하기
                                        </button>
                                    </div>

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

                <!-- 페이지네이션 시작 -->
                <nav th:unless="${#lists.isEmpty(itemReviews)}" class="mt-4">
                    <ul class="pagination justify-content-center">
                        <li class="page-item" th:classappend="${page == 0} ? 'disabled'">
                            <a class="page-link"
                               th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${page - 1}, size=${size})}">이전</a>
                        </li>
                        <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}"
                            th:classappend="${page == i} ? 'active'">
                            <a class="page-link"
                               th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${i}, size=${size})}"
                               th:text="${i + 1}"></a>
                        </li>
                        <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'">
                            <a class="page-link"
                               th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${page + 1}, size=${size})}">다음</a>
                        </li>
                    </ul>
                </nav>
                <!--페이지네이션 끝-->


                <!-- End Contact -->


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


<footer th:replace="frame/footer :: footer"></footer>

</body>

</html>

 

 

 

 

 

 

itemReview js에 유효성 검사 

itemReview.js 유효성 검사 부분 추가, summernote도 itemReviewPost.html에서 여기로 옮김


let itemBoardObject = {
    init: function() {
        let _this = this;

        $("#btn-itemReviewPost").on("click", function() {
            _this.insert();
        }),
        $("#btn-itemReviewUpdate").on("click", function() {

              _this.update();
                }),

        $(document).on("click", ".btn-delete-itemReview", function() {
          // 클릭된 버튼의 데이터 속성에서 itemReviewId를 가져옴
          let itemReviewId = $(this).data("itemreviewid");
          // 아이템 리뷰 삭제 함수 호출
           _this.deleteItemReview(itemReviewId);
             });


        // Summernote 초기화
          $("#itemReviewContent").summernote({
            height: 300,
            placeholder: '내용을 입력하세요...'
            });



    },

    insert: function() {


        let itemId = $("#itemId").val();  // URL에 사용할 itemId를 가져옴
        let itemScore = $("#itemScore").val(); // 별점 가져오기
        let itemReviewName = $("#itemReviewName").val(); // 제목 가져오기
        let itemReviewContent = $("#itemReviewContent").summernote('code'); //썸머노트 내용 가져오기


                // 별점을 선택하지 않은 경우 처리
                if (itemScore === "0") {
                    alert("별점을 선택하세요.");
                    return; // 리뷰 등록 중단
                }

                // 제목이 비어 있는지 확인
                 if (!itemReviewName || itemReviewName.trim().length === 0) {
                      alert("제목을 입력하세요.");
                      return; // 리뷰 등록 중단
                  }

                // 썸머노트에서 가져온 HTML 태그 제거 및 내용 검사
                 let plainTextContent = stripTags(itemReviewContent).trim();

                    if (!plainTextContent || plainTextContent.length === 0) {
                        alert("내용을 입력하세요.");
                        return;
                    }



        let data = {
            itemId: itemId,  // itemId를 데이터에 포함
            memberId: $("#memberId").val(),  // 멤버 ID 추가
            itemReviewName: $("#itemReviewName").val(),
            itemScore: $("#itemScore").val(),
            itemReviewContent: $("#itemReviewContent").val(),
        };





        $.ajax({
            type: "POST",
            url: "/ypjs/itemReview/post/" + itemId,  // URL에 itemId를 추가
            data: JSON.stringify(data),
            contentType: "application/json; charset=utf-8",
            success: function(response) {
                alert("리뷰가 등록되었습니다.");
                window.location.href = "/ypjs/itemReview/get/" + itemId;  // 성공 후 리디렉션
            },
            error: function(error) {
                alert("에러 발생: " + JSON.stringify(error));
            }
        });
    },


     update: function() {

        let itemReviewId = $("#itemReviewId").val();
        let itemScore = $("#itemScore").val(); // 별점 가져오기
        let itemReviewName = $("#itemReviewName").val(); // 제목 가져오기
        let itemReviewContent = $("#itemReviewContent").summernote('code'); //썸머노트 내용 가져오기


          // 별점을 선택하지 않은 경우 처리
           if (itemScore === "0") {
               alert("별점을 선택하세요.");
               return; // 리뷰 등록 중단
           }

         // 제목이 비어 있는지 확인
          if (!itemReviewName || itemReviewName.trim().length === 0) {
               alert("제목을 입력하세요.");
               return; // 리뷰 등록 중단
          }

          // 썸머노트에서 가져온 HTML 태그 제거 및 내용 검사
         let plainTextContent = stripTags(itemReviewContent).trim();
          if (!plainTextContent || plainTextContent.length === 0) {
              alert("내용을 입력하세요.");
               return;
                            }



            let updateData = {

                itemReviewName: $("#itemReviewName").val(),
                itemScore: $("#itemScore").val(),
                itemReviewContent: $("#itemReviewContent").val(),
            };

            $.ajax({
                type: "PUT",

                url: "/ypjs/itemReview/update/" + itemReviewId,
                data: JSON.stringify(updateData),
                contentType: "application/json; charset=utf-8",
                success: function(response) {
                alert("리뷰가 수정되었습니다.");
                window.location.href = "/ypjs/itemReview/get/" + response ;

                },
                error: function(error) {
                    alert("에러 발생: " + JSON.stringify(error));
                    // 에러 발생 시 적절히 처리하도록 수정이 필요할 수 있습니다.
                }
            });
        },




         deleteItemReview: function(itemReviewId) {

                 let itemId = $("#itemId").val();

                 $.ajax({
                     type: "DELETE",
                     url: "/ypjs/itemReview/delete/" + itemReviewId,
                     success: function(response) {
                         alert("리뷰가 삭제되었습니다.");

                         window.location.href = "/ypjs/itemReview/get/" + itemId;
                     },
                     error: function(xhr, status, error) {
                         alert("에러 발생: " + error);
                     }
                 });
             }


        };



$(document).ready(function() {
    itemBoardObject.init();
});



// HTML 태그 제거 함수
function stripTags(html) {
    let tmp = document.createElement("DIV");
    tmp.innerHTML = html;
    return tmp.textContent || tmp.innerText || "";
}

 

 

 

카테고리 유효성 검사

category.js에 유효성 검사 추가


let categoryBoardObject = {
    init: function() {
        let _this = this;

        $("#btn-categoryPost").on("click", function() {

            _this.insert();
        }),

        $("#btn-categoryUpdate").on("click", function() {

            _this.update();
        }),

        $("#btn-categoryDelete").on("click", function() {

            _this.delete(); // 수정된 부분: delete 함수 호출
        });
    },

    insert: function() {

       let categoryParent = $("#categoryParent").val();
       let categoryName = $("#categoryName").val();

       if (!categoryParent || categoryParent.trim().length === 0) {
           alert("카테고리 부모 번호를 입력하세요");
           return;
       }

       if(!categoryName || categoryName.trim().length === 0){
           alert("카테고리 이름을 입력하세요");
           return;
       }

        let data = {
            categoryParent: $("#categoryParent").val(),
            categoryName: $("#categoryName").val(),
        };

        $.ajax({
            type: "POST",
            url: "/ypjs/category/post",
            data: JSON.stringify(data),
            contentType: "application/json; charset=utf-8",
            success: function(response) {
             alert("카테고리 저장되었습니다.");
                window.location.href = "/ypjs/category/get";
            },
            error: function(error) {
                window.location.href = "/ypjs/category/get";
            }
        });
    },

    update: function() {
        let categoryId = $("#categoryId").val();
        let categoryParent = $("#categoryParent").val();
        let categoryName = $("#categoryName").val();


         if (!categoryId || categoryId.trim().length === 0) {
              alert("카테고리 번호를 입력하세요");
              return;
          }


         if (!categoryParent || categoryParent.trim().length === 0) {
            alert("카테고리 부모 번호를 입력하세요");
            return;
          }

        if(!categoryName || categoryName.trim().length === 0){
           alert("카테고리 이름을 입력하세요");
           return;
        }

        let updateData = {
            categoryId: categoryId, // 수정된 부분: categoryId 추가
            categoryParent: $("#categoryParent").val(),
            categoryName: $("#categoryName").val(),
        };

        $.ajax({
            type: "PUT",
            url: "/ypjs/category/update/" + categoryId,
            data: JSON.stringify(updateData),
            contentType: "application/json; charset=utf-8",
            success: function(response) {
            alert("카테고리가 수정되었습니다.");
                window.location.href = "/ypjs/category/get";
            },
            error: function(error) {
                alert("에러 발생: " + JSON.stringify(error));
            }
        });
    },

    delete: function() {
        let categoryId = $("#categoryId").val(); // 삭제할 categoryId 가져오기

        $.ajax({
            type: "DELETE",
            url: "/ypjs/category/delete/" + categoryId,
            success: function(response) {
                  alert("카테고리가 삭제되었습니다.");
                window.location.href = "/ypjs/category/get"; // 삭제 후 페이지 이동
            },
            error: function(xhr, status, error) {
                alert("에러 발생: " + error);
            }
        });
    }
};

$(document).ready(function() {
    categoryBoardObject.init();
});

 

 

 

아이템 유효성 검사

itemPost.html, itemUpdate.html은 form으로 넘겨서 따로 itemValidation.js만들어서 유효성 검사,

썸머노트, 썸네일부분 script도 여기로 옮김

$(document).ready(function () {
    // Summernote 초기화
    $("#itemContent").summernote({
        height: 500,
        placeholder: '상품 내용을 입력하세요...',
        callbacks: {
            onChange: function (contents, $editable) {
                // 내용이 변경될 때 추가 로직
            }
        }
    });

    // 파일명 표시 함수
    function displayFileName(input) {
        if (input.files.length > 0) {
            var fileName = input.files[0].name;
            document.getElementById('file-name-display').innerText = '선택된 파일: ' + fileName;
        } else {
            document.getElementById('file-name-display').innerText = '';
        }
    }

    // 폼 제출 전 유효성 검사
    $('#itemForm').submit(function (event) {
        var categoryId = $("#categoryId").val();
        var itemName = $("#itemName").val();
        var itemPrice = $("#itemPrice").val();
        var itemStock = $("#itemStock").val();
        var itemContent = $("#itemContent").summernote('code');

        if (!categoryId || categoryId.trim().length === 0) {
            alert('카테고리 번호를 입력하세요.');
            event.preventDefault(); // 폼 제출 방지
            return;
        }

        if (!itemName || itemName.trim().length === 0) {
            alert('상품명을 입력하세요.');
            event.preventDefault(); // 폼 제출 방지
            return;
        }

        if (!itemPrice || itemPrice.trim().length === 0) {
            alert('상품가격을 입력하세요.');
            event.preventDefault(); // 폼 제출 방지
            return;
        }

        if (!itemStock || itemStock.trim().length === 0) {
            alert('상품수량을 입력하세요.');
            event.preventDefault(); // 폼 제출 방지
            return;
        }

         // 썸머노트에서 가져온 HTML 태그 제거 및 내용 검사
         let plainTextContent = stripTags(itemContent).trim();

        if (!plainTextContent || plainTextContent.trim().length === 0) {
            alert('상품내용을 입력하세요.');
            event.preventDefault(); // 폼 제출 방지
            return;
        }

        var fileInput = document.getElementById('file');
        if (fileInput.files.length === 0) {
            alert('썸네일 파일을 선택하세요.');
            event.preventDefault(); // 폼 제출 방지
        }
    });
});


// HTML 태그 제거 함수
function stripTags(html) {
    let tmp = document.createElement("DIV");
    tmp.innerHTML = html;
    return tmp.textContent || tmp.innerText || "";
}

 

 

 

 

itemList.html, itemCategoryList.html, itemGet.html에 있던 별 script

itemRatingsGet.js따로 만듦

document.addEventListener("DOMContentLoaded", function() {
    var starRatingContainers = document.querySelectorAll("#starRating");

    starRatingContainers.forEach(function(starRatingContainer) {
        var rating = parseFloat(starRatingContainer.getAttribute("data-rating"));
        starRatingContainer.innerHTML = generateStarRating(rating);
    });

    // 별점을 생성하여 HTML 문자열 반환하는 함수 (순수 JavaScript)
    function generateStarRating(rating) {
        var starsHTML = '';
        var numStars;

        if (rating >= 0 && rating < 1) {
            numStars = 0;
        } else if (rating >= 1 && rating < 2) {
            numStars = 1;
        } else if (rating >= 2 && rating < 3) {
            numStars = 2;
        } else if (rating >= 3 && rating < 4) {
            numStars = 3;
        } else if (rating >= 4 && rating < 5) {
            numStars = 4;
        } else if (rating == 5) {
            numStars = 5;
        }

        for (var i = 0; i < numStars; i++) {
            starsHTML += '<i class="text-warning fa fa-star"></i>';
        }

        for (var i = numStars; i < 5; i++) {
            starsHTML += '<i class="text-muted fa fa-star"></i>';
        }

        return starsHTML;
    }
});

 

 

itemReviewPost.html, itemReviewUpdate.html에 있던 별 js파일 따로 만듦

itemRatingsPost.js

$(document).ready(function() {
    var initialScore = $('#itemScore').val(); // 기존의 itemScore 값 가져오기
    $('#starRating').html(generateStarRating(initialScore)); // 초기화: 기존의 별점으로 별 아이콘 표시

    // 별 아이콘 클릭 이벤트 리스너 추가
    $('#starRating').on('click', '.fa-star', function() {
        var score = $(this).data('score');
        $('#itemScore').val(score); // hidden 필드에 새로운 점수 설정
        $('#starRating').html(generateStarRating(score)); // 별 아이콘 업데이트
    });

    // 별 아이콘을 생성하여 HTML 문자열 반환하는 함수
    function generateStarRating(score) {
        var starsHTML = '';
        for (var i = 1; i <= 5; i++) {
            if (i <= score) {
                starsHTML += '<i class="fas fa-star rating-stars filled-star" data-score="' + i + '"></i>';
            } else {
                starsHTML += '<i class="fas fa-star rating-stars" data-score="' + i + '"></i>';
            }
        }
        return starsHTML;
    }
});



 

둘이 합치고 싶었지만 그러면 둘 중 하나만 적용 됨

 

 

리뷰 갯수 추가

//item1개 조회
@GetMapping("/ypjs/item/get/{itemId}")
public String getOneItem (@PathVariable("itemId") Long itemId,
                              Model model) {

    Item findItem = itemService.findOneItem(itemId);

    ItemOneDto item = new ItemOneDto(findItem);


    model.addAttribute("item", item);

    //조회수
    itemService.increaseItemCnt(itemId);

    //리뷰 갯수
    int reviewCount = itemReviewService.countAllItemReview(itemId);
    model.addAttribute("reviewCount", reviewCount);

    return "item/itemGet";

}

 

서비스, 레파지토리는 위에 있음

 

item/itemGet

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">

<head th:replace="frame/header :: head">
    <title>Zay Shop - Product Listing Page</title>
</head>

<script src="/js/item/itemRatings.js"></script>

<body>
<!--header-->
<header th:replace="frame/header :: header"></header>
<script src="/js/item/itemRatingsGet.js"></script>
<script src="/js/item/item.js"></script>

<style>
    .bg-light {
        background-color: transparent !important; /* 배경색을 투명하게 설정 */
    }

    /* 요소의 우선순위를 높여서 Bootstrap의 font-weight-bold 클래스가 적용될 수 있도록 함 */
    h1.font-weight-bold, span.font-weight-bold {
        font-weight: bold !important;
    }

     .custom-mt {
        margin-top: -20px; /* 원하는 간격 값으로 설정 */
    }
<!--    가운데 정렬-->

    .centered-content {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }
        #itemContent {
            text-align: center;
        }


     /* #itemContent를 flexbox로 설정하여 가운데 정렬 */
    #itemContent {
        display: flex;
        flex-direction: column; /* 내부 요소들을 세로로 정렬하기 위해 */
        justify-content: center; /* 수직 가운데 정렬 */
        align-items: center; /* 수평 가운데 정렬 */
    }

    /* 썸머노트에서 삽입된 이미지의 스타일 */
    #itemContent img {
        max-width: 100%; /* 최대 너비 설정 */
        height: auto; /* 높이 자동으로 조정 */
        margin-bottom: 10px; /* 이미지 아래 여백 추가 */
    }

    /* 썸머노트에서 삽입된 텍스트의 스타일 */
    #itemContent p {
        text-align: center; /* 텍스트 가운데 정렬 */
        margin-bottom: 10px; /* 문단 아래 여백 추가 */
    }

</style>






<!-- Open Content -->





<div class="card-body pt-4 mt-3"> <!-- pt-4를 mt-3으로 변경 -->
    <div class="row">
        <div class="col-lg-7 offset-lg-1">
            <h1 class="h2 mb-3 font-weight-bold" th:text="${item.itemName}"></h1>
            <span id="itemPrice" class="font-weight-bold" th:text="${item.itemPrice}"></span>
            <span class="font-weight-bold">원</span>
            <p class="h3 py-2"></p>
        </div>
    </div>

    <!-- 별점 및 평점 표시 -->
    <div class="row mt-3">
        <div class="col-lg-7 offset-lg-1">
            <div class="d-flex justify-content-start align-items-center">
                <ul class="list-unstyled d-flex mb-0 mr-3 text-left" id="starRating" th:attr="data-rating=${item.itemRatings}">
                    <!-- 별 아이콘은 JavaScript로 생성됩니다 -->
                </ul> &nbsp;&nbsp;&nbsp;&nbsp;
                <span id="itemRatings"  th:text="${item.itemRatings}"></span> &nbsp;&nbsp;&nbsp;&nbsp;
                <div>
                    <a th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${item.itemId})}">
                        Reviews  &nbsp;(<span th:text="${reviewCount}"></span>)
                    </a>
                </div>
            </div>
        </div>
    </div>




    <!-- 수정하기 및 삭제하기 버튼 -->
    <div class="row justify-content-end mt-3">
        <div class="col-auto">
            <a th:href="@{/ypjs/item/update/{itemId}(itemId=${item.itemId})}" class="btn btn-warning">수정하기</a>
        </div>
        <div class="col-auto">
            <input type="hidden" id="itemId" th:value="${item.itemId}">
            <button type="button" id="btn-itemDelete" class="btn btn-danger">삭제하기</button>
        </div>
    </div>

            <div class="row justify-content-end mt-3">
                <div class="col-auto">
                    <span class="font-weight-bold"> 조회수 : </span>
                    <span id="itemCnt" class="font-weight-bold" th:text="${item.itemCnt}"></span>
                    <br>
                    <span class="font-weight-bold">작성일 : </span>
                    <span id="itemCreateDate" class="font-weight-bold" th:text="${#temporals.format(item.itemCreateDate, 'yyyy-MM-dd')}"></span>
                </div>
                <div class="col-auto">

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

<hr>





<section class="bg-light">
    <div class="container pb-5">
        <div class="row justify-content-center align-items-center">
            <div class="col-lg-5 mt-5 text-center">
                <div id="itemContent" th:utext="${item.itemContent}">
                    <!-- 썸머노트에서 입력된 내용 (글과 이미지) -->
                </div>
            </div>
        </div>
    </div>
</section>




<div class="row pb-3">
    <div class="col-lg-6 offset-lg-5"> <!-- offset-lg-6 클래스 추가하여 오른쪽으로 옮김 -->
        <ul class="list-inline pb-3 text-right"> <!-- text-right 클래스 추가하여 내용 오른쪽 정렬 -->
            <li class="list-inline-item">
                수량
                <input type="hidden" name="product-quanity" id="product-quanity" value="1">
            </li>
            <li class="list-inline-item"><span class="btn btn-success" id="btn-minus">-</span></li>
            <li class="list-inline-item"><span class="badge bg-secondary" id="var-value">1</span></li>
            <li class="list-inline-item"><span class="btn btn-success" id="btn-plus">+</span></li>
        </ul>
    </div>
</div>

<div class="row pb-3">
    <div class="col d-grid">
        <button type="submit" class="btn btn-success btn-md" name="submit" value="buy">Buy</button>
    </div>
    <div class="col d-grid">
        <button type="submit" class="btn btn-success btn-md" name="submit" value="addtocard">Add To Cart</button>
    </div>
</div>

</form>

                    </div>
                </div>
            </div>
        </div>
    </div>
</section>
<!-- Close Content -->






<footer th:replace="frame/footer :: footer"></footer>

</body>

</html>

 

reviewCount부분 추가

 

 

 

 

리뷰 갯수 링크에 같이 연결돼서 나옴

 

 

 

 

 

리뷰 정렬

기본 최신순, 별점 높은 순, 별점 낮은 순 정렬

 

itemReviewRepository

//아이템 당 리뷰 조회
public List<ItemReview> findAllItemReview(Long itemId, Pageable pageable, String sortBy) {
    // 기본 쿼리 문자열
    String queryString = "select ir from ItemReview ir join fetch ir.item i where i.itemId = :itemId";

    // 정렬 조건 추가
    switch (sortBy) {
        case "highScore":
            queryString += " order by ir.itemScore desc, ir.itemReviewId desc"; // 'highScore'일 때 itemScore 기준 내림차순, 동일할 경우 itemReviewId 내림차순 정렬
            break;
        case "lowScore":
            queryString += " order by ir.itemScore asc, ir.itemReviewId desc"; // 'lowScore'일 때 itemScore 기준 오름차순, 동일할 경우 itemReviewId 내림차순 정렬
            break;
        default:
            queryString += " order by ir.itemReviewId desc"; // 기본 정렬: itemReviewId 기준 내림차순 정렬 (최신순)
            break;
    }

    return em.createQuery(queryString, ItemReview.class)
            .setParameter("itemId", itemId)
            .setFirstResult((int) pageable.getOffset())
            .setMaxResults(pageable.getPageSize())
            .getResultList();
}

 

 

 

itemReviewService

//아이템 당 리뷰조회
public List<ItemReviewListDto> findAllItemReview(Long itemId, Pageable pageable, String sortBy) {
    List<ItemReview> reviews = itemReviewRepository.findAllItemReview(itemId, pageable, sortBy);

    List<ItemReviewListDto> result = reviews.stream()
            .map(ItemReviewListDto::new)
            .collect(Collectors.toList());

    return result;
}

 

 

itemReviewController

//아이템 당 리뷰조회
@GetMapping("/ypjs/itemReview/get/{itemId}")
public String getAllItemReview(@PathVariable(name = "itemId") Long itemId, Model model,
                               @RequestParam(value = "page",defaultValue = "0") int page,
                               @RequestParam(value = "sortBy", defaultValue = "itemReviewId") String sortBy,
                               @RequestParam(value = "size",defaultValue = "10") int size) {

    Pageable pageable = PageRequest.of(page, size);

    itemService.findOneItem(itemId);

    List<ItemReviewListDto> itemReviews = itemReviewService.findAllItemReview(itemId, pageable, sortBy);

    //총 페이지 수 계산
    int totalPages = Page.totalPages(itemReviewService.countAllItemReview(itemId), size);

    model.addAttribute("itemReviews", itemReviews);
    model.addAttribute("sortBy", sortBy); // 정렬 옵션을 다시 모델에 추가
    model.addAttribute("page",page); //페이징
    model.addAttribute("size",size); //페이징
    model.addAttribute("totalPages", totalPages); //총 페이지 수


    return "itemreview/itemReviewGet";
}

 

 

 

itemreview/itemReviewGet

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">

<head th:replace="frame/header :: head">
    <title>Zay Shop - Product Listing Page</title>
</head>

<script src="/js/item/itemRatingsGet.js"></script>

<body>
<!--header-->
<header th:replace="frame/header :: header"></header>

<style>
    .category-box {
        border: 1px solid #ccc;
        padding: 10px;
        margin-bottom: 10px;
    }

    .category-label {
        font-weight: bold;
    }

    .item-box {
                    border: 1px solid #ccc;
                    padding: 10px;
                    margin-bottom: 10px;
                    max-width: 1000px; /* 최대 너비 설정 */
                    margin-left: auto;
                    margin-right: auto;
                }

.item-info {
/* Optional: Add additional styles for item information */
}

.category-label {
font-weight: bold;
}

 .h3-indent {
                    margin-left: 100px; /* 왼쪽 여백을 줌으로써 텍스트를 오른쪽으로 이동 */
                }



     .custom-select-wrapper {
        border: 1px solid #f0f8ff; /* 테두리 두께와 색상 설정 */
        padding: 3px; /* 선택 영역의 여백 설정 */
        background-color: #f0f8ff; /* 연한 하늘색 배경색 */
        display: inline-block; /* 인라인 요소로 표시 */
        border-radius: 10px; /* 테두리 둥글기 설정 */
    }

    .form-control {
        border: 1px solid #f0f8ff; /* 내부 테두리 설정 */
        border-radius: 8px; /* 안쪽 테두리 둥글기 설정 */
    }

</style>


<!-- Start Content -->
<div class="container py-5">
    아이템 리뷰
</div>
<!-- End Content -->


<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<script src="/js/item/itemReview.js"></script>


<!-- Start Contact -->
<!--정렬 추가-->
<form th:action="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0]?.itemId})}" method="get" class="row">
    <div class="offset-sm-9 col-sm-1 mb-2 custom-select-wrapper" style="width: 8%;">
        <select class="form-control" name="sortBy" id="sortBy" onchange="this.form.submit()">
            <option th:value="itemReviewId" th:selected="${sortBy == 'itemReviewId'}">최신순</option>
            <option th:value="highScore" th:selected="${sortBy == 'highScore'}">별점 높은 순</option>
            <option th:value="lowScore" th:selected="${sortBy == 'lowScore'}">별점 낮은 순</option>
        </select>
    </div>
</form>








    <div class="container py-2">
    <div class="row py-5">
            <!-- 폼 요소들 -->
            <div class="row">
                <div th:if="${not #lists.isEmpty(itemReviews)}">
                    <h3 class="h3-indent">리뷰 목록</h3>
                    <br>
                    <div th:each="itemReview : ${itemReviews}" class="item-box">
                        <div class="item-info" style="text-align: right;">
                            <div>
                                <span style="font-weight: bold;">작성일 : </span>
                                <span id="itemReviewCreateDate"
                                      th:text="${#temporals.format(itemReview.itemReviewCreateDate, 'yyyy-MM-dd')}"></span>
                            </div>
                        </div>


                        <div class="item-info">
                            <div>
                                <span class="category-label">제목&nbsp;&nbsp;:&nbsp;&nbsp;</span>
                                <span th:text="${itemReview.itemReviewName}"></span>
                            </div>

                            <!-- 별점 및 평점 표시 -->
                            <div class="d-flex justify-content-left mb-1 align-items-left">
                                <span class="category-label">별점&nbsp;&nbsp;:&nbsp;&nbsp;</span>
                                <ul class="list-unstyled d-flex mb-0" id="starRating"
                                    th:attr="data-rating=${itemReview.itemScore}">
                                    <!-- 별 아이콘은 JavaScript로 생성됩니다 -->
                                </ul>

                            </div>


                            <div>
                                <span class="category-label">리뷰 내용&nbsp;&nbsp;:&nbsp;&nbsp;</span>
                                <div style="text-align: left;">
                                    <span th:utext="${itemReview.itemReviewContent}"></span>
                                </div>
                            </div>


                            <input type="hidden" id="itemId" th:value="${itemReview.itemId}">
                            <!-- 수정 및 삭제 버튼 -->
                            <div class="container">
                                <div class="row justify-content-end">
                                    <div class="col-auto">
                                        <a th:href="@{/ypjs/itemReview/update/{itemReviewId}(itemReviewId=${itemReview.itemReviewId})}"
                                           class="btn btn-warning">수정하기</a>
                                    </div>
                                    <div class="col-auto">

                                        <button type="button"
                                                class="btn btn-danger btn-sm btn-delete-itemReview"
                                                th:data-itemReviewId="${itemReview.itemReviewId}"
                                                style="padding: 2px 10px; font-size: 16px; width: 90px; height: 40px;">
                                            삭제하기
                                        </button>
                                    </div>

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

                <!-- 페이지네이션 시작 -->
                <nav th:unless="${#lists.isEmpty(itemReviews)}" class="mt-4">
                    <ul class="pagination justify-content-center">
                        <li class="page-item" th:classappend="${page == 0} ? 'disabled'">
                            <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${(page > 0 ? page - 1 : 0)}, size=${size}, sortBy=${sortBy})}">이전</a>
                        </li>
                        <li class="page-item" th:each="i : ${#numbers.sequence(0, totalPages - 1)}" th:classappend="${page == i} ? 'active'">
                            <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${i}, size=${size}, sortBy=${sortBy})}" th:text="${i + 1}"></a>
                        </li>
                        <li class="page-item" th:classappend="${page == totalPages - 1} ? 'disabled'">
                            <a class="page-link" th:href="@{/ypjs/itemReview/get/{itemId}(itemId=${itemReviews[0].itemId}, page=${(page < totalPages - 1 ? page + 1 : totalPages - 1)}, size=${size}, sortBy=${sortBy})}">다음</a>
                        </li>
                    </ul>
                </nav>


                <!--페이지네이션 끝-->


                <!-- End Contact -->


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


<footer th:replace="frame/footer :: footer"></footer>

</body>

</html>

 

스타일, 정렬부분 추가