하루 늦은 기록 

 

프론트 디자인 및 이벤트 구현 완료

 

지금은 menu와 카테고리 더미데이터만 불러와서 테스트 해봤는데

 

다음주에는 결재하고 인증키 까지 받아서 db 에 저장하고 환불 하는 기능까지 해볼 예정

 

main 부분이 많이 바뀌진 않았지만 버그 수정 및 팀장님이 말하던 아이콘 위치나 이벤트는 완료

 

main.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8" />
    <title></title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/main.css" rel="stylesheet" type="text/css">
    <script src="/js/user/main.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <div class="restaurant-image">
            <img src="/images/sample.png" alt="식당 메인 이미지" class="restaurant-img">
        </div>
        <div class="title">
            <div class="restaurant-name">더조은 식당</div>
            <button class="report-button button">
                <i class="fas fa-exclamation-triangle"></i>신고하기
            </button>
        </div>
        <div class="ratings-reviews">
            <div class="ratings">
                <div class="stars">
                    <span><i class="far fa-star"></i></span>
                    <span><i class="far fa-star"></i></span>
                    <span><i class="far fa-star"></i></span>
                    <span><i class="far fa-star"></i></span>
                    <span><i class="far fa-star"></i></span>
                </div>  <!-- 5개의 별을 기본으로 설정 -->
                <label>3.8</label> <!-- 예시 평점 -->
            </div>
            <button class="call-button button">호출하기</button>
        </div>
        <div class="buttons">
            <button class="menu-btn button tab-btn" data-target="menu-content">메뉴</button>
            <button class="announcement-btn button tab-btn" data-target="announcement-content">공지사항</button>
            <button class="reviews-btn button tab-btn" data-target="review-content">리뷰</button>
        </div>
        <div class="content">
            <div class="menu-content tab-content">
                <div class="category-content">
                    <div th:each="category : ${categories}" class="category" th:text="${category.name}" th:data-targets="'menu-category-' + ${category.id}"></div>
                </div>
                <div class="menus-container">
                    <div th:each="category : ${categories}" class="menu-category"  th:id="'menu-category-' + ${category.id}" th:data-category-id="${category.id}" >
                        <div th:text="${category.name}" class="category-name"></div>
                        <div class="menus" th:each="menu : ${menus}">
                            <div th:if="${menu.category.id} == ${category.id}" class="menu" th:onclick="'location.href=\'/menu/info?id=' + ${menu.id} + '\''">
                                <div class="image">
                                    <img src="/images/sample.png" alt="메뉴 이미지" class="restaurant-image">
                                </div>
                                <div class="text">
                                    <div th:text="${menu.name}" class="menu-name"></div>
                                    <!--th:text="${menu.info}"-->
                                    <div class="menu-info">맛있음</div>
                                    <div th:text="${menu.price + '원'}" class="menu-price"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="announcement-content tab-content">
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항1</div>
                        <div class="date">16:12</div>
                    </div>
                    <div class="content">쉬고싶어서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항2</div>
                        <div class="date">2024.04.03</div>
                    </div>
                    <div class="content">아파서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항3</div>
                        <div class="date">2024.04.02</div>
                    </div>
                    <div class="content">싱숭생숭해서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항4</div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">해어져서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항5</div>
                        <div class="date">2024.03.20</div>
                    </div>
                    <div class="content">그냥 쉽니다</div>
                </div>
            </div>
            <div class="review-content tab-content">
                <div class="review">
                    <div class="title">박윤재</div>
                    <div class="mid">
                        <!-- 여기에 별점을 표시할 div를 추가합니다. 데이터 속성(data-rating)을 사용하여 별점을 지정합니다. -->
                        <div class="star" data-rating="5"></div>
                        <div class="date">1달전</div>
                    </div>
                    <div class="content">음식이 친절하고 사장님이 맛있어요</div>
                </div>
                <div class="review">
                    <div class="title">손지영</div>
                    <div class="mid">
                        <div class="star" data-rating="4"></div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">1주전</div>
                </div>
                <div class="review">
                    <div class="title">윤경재</div>
                    <div class="mid">
                        <div class="star" data-rating="1"></div>
                        <div class="date">2시간전</div>
                    </div>
                    <div class="content">집에 보내주세요</div>
                </div>
            </div>
        </div>
    </div>
    <footer></footer>
    <template  th:replace="~{user/modal/modal :: modal}" ></template>
</div>
</body>
</html>

 

main.css

@charset "UTF-8";

/* 메인 이미지 */
.container .restaurant-image{
    width: 100%;
    height: 200px;
    overflow: hidden;
}

.container .restaurant-image img{
    width: 100%;
    object-fit: cover;
    object-position: center top;
}

/* 타이틀과 신고버튼 */
.container .title{
    width: 100%;
    padding: var(--padding);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* 타이틀 */
.container .title .restaurant-name{
    font-size: var(--font-big);
    font-weight: bold;
}

/* 신고 */
.container .title .report-button{
    padding: 0;
    background-color: transparent;
    color: var(--red);
    border: none;
    font-size: var(--font-small);
}

.container .title .report-button .fas{
    margin-right: 5px;
}

/* 별표와 리뷰 버튼*/
.container .ratings-reviews{
    margin-bottom: 10px;
    padding: var(--padding);
    padding-top: 0;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* 별표 부분*/
.container .ratings-reviews .ratings {
    display: flex;
    align-items: center;
}

/* 별표의 별 부분 */
.container .ratings-reviews .ratings .stars{
    position: relative;
    display: inline-block;
    font-size: var(--font-content); /* 별 크기 조정 */
    direction: ltr; /* 별을 왼쪽에서 오른쪽으로 채워나가기 위해 */
}

/* 별표의 숫자 부분 */
.container .ratings-reviews .ratings label{
    margin-left: 10px;
}

/* 호출 버튼 부분*/
.container .ratings-reviews .call-button{
    border: 0;
    border-radius: 0;
    padding: 10px 30px;
}

/* 메뉴 공지사항 리뷰 */
.container .buttons{
    border-bottom: 3px solid var(--orange);
    display: flex;
    width: 100%;
}

/* 메뉴 */
.container .buttons .menu-btn{
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 공지사항 */
.container .buttons .announcement-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 리뷰 */
.container .buttons .reviews-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 버튼을 눌렀을때 보여주는 콘텐츠 영역 */
.container .content{
    width: 100%;
}

/* 메뉴 콘텐츠 */
.container .content .menu-content{
    width: 100%;
}

/* 카테고리 슬라이더 부분 */
.container .content .menu-content .category-content{
    display: flex; /* 이 부분을 추가합니다 */
    overflow-x: auto; /* 수평 스크롤을 위해 추가합니다 */
    background-color: var(--white);
    scrollbar-width: none;
    z-index: 1; /* 다른 요소들 위에 오도록 z-index 설정 */
}

.sticky {
    position: fixed;
    top: 0; /* 또는 header 높이에 맞추어 조정 */
    width: var(--innerBox-width);
    box-shadow: 0 2px 5px rgba(0,0,0,0.2); /* 선택사항: 고정된 요소에 그림자 효과 추가 */
}

.container .content .menu-content .category-content .category{
    margin: 10px;
    padding: 10px 20px;
    flex: 0 0 auto; /* flex 항목이 자신의 크기를 유지하도록 설정합니다 */
    border-radius: 20px;
    text-align: center;
    background-color: var(--white);
    color:var(--black);
    cursor: pointer; /* 마우스 오버 시 커서 변경을 위해 추가합니다 */
    border: 1px solid var(--gray);
    transition: background-color 0.3s, color 0.3s; /* 부드러운 색상 전환 */
}
.container .content .menu-content .category-content .category.active{
    background-color: var(--gray);
    color: var(--white);
}
.container .content .menu-content .category-content:last-child{
    margin-right: 0;
}

/* 메뉴 리스트 */
.container .content .menu-content .menus-container {
}
.container .content .menu-content .menus-container .category-name{
    padding:var(--padding);
    background-color: var(--extra-light-gray);
    color: var(--gray);
    font-size: var(--font-middle);
    font-weight: bold;
    border-bottom: 2px solid var(--light-gray);
}
.container .content .menu-content .menus-container .menu{
    width: 100%;
    display: flex;
    border-bottom: 2px solid var(--light-gray);
    cursor: pointer;
}
.container .content .menu-content .menus-container .menu .image{
    padding: 10px;
    width: 120px;
    height: 120px;
    overflow: hidden;
}
.container .content .menu-content .menus-container .menu .image img{
    width: 100%;
    height: 100%;
}
.container .content .menu-content .menus-container .menu .text{
    padding: 10px;
    display: flex;
    flex-direction: column;
}
.container .content .menu-content .menus-container .menu .text .menu-name{
    margin-bottom: 10px;
    font-size: var(--font-content);
    font-weight: bold;
    color:var(--black);
}
.container .content .menu-content .menus-container .menu .text .menu-info{
    margin-bottom: 10px;
    flex: 1;
    font-size: var(--font-content);
    color:var(--gray);
}
.container .content .menu-content .menus-container .menu .text .menu-price{
    font-size: var(--font-content);
    color:var(--black);
}

/* 공지사항 콘텐츠 */
.container .content .announcement-content{
    width: 100%;
    display: none;
}
.container .content .announcement-content .announcement {
    width: 100%;
    display: flex;
    flex-direction: column;
    padding: 20px;
    border-bottom: 2px solid var(--light-gray);
}
.container .content .announcement-content .announcement .title {
    padding:0;
    font-size: var(--font-middle);
    color: var(--black);
    margin-bottom: 20px;
}
.container .content .announcement-content .announcement .date{
    padding:0;
    font-size: var(--font-small);
    color: var(--light-gray);
}
.container .content .announcement-content .announcement .content{
    padding:0;
    font-size: var(--font-content);
    color: var(--gray);
}

/* 리뷰 */
.container .content .review-content{
    width: 100%;
    display: none;
    padding: var(--padding);
}
.container .content .review-content .review{
    margin-bottom: 20px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    border: 2px solid var(--light-gray);
    padding: var(--padding);
}
.container .content .review-content .review .title{
    padding: 0;
    font-size: var(--font-content);
    color: var(--black);
    margin-bottom: 5px;
}
.container .content .review-content .review .mid{
    display: flex;
    font-size: var(--font-small);
    color: var(--light-gray);
    flex: 1;
    padding-bottom: 20px;
}
.container .content .review-content .review .mid .star span{
    font-size: var(--font-small);
}
.container .content .review-content .review .mid .star span .fas {
    color: var(--gold); /* 노란색 별 */
}
.container .content .review-content .review .mid .star span .far{
    color: var(--light-gray); /* 회색 별 */
}
.container .content .review-content .review .mid .date{
    margin-left: 10px;
}
.container .content .review-content .review .content{
    font-size: var(--font-content);
    color: var(--black);
}

 

main.js

/* 식당 평점 별 부분*/
function ratingStar(){
    const rating = document.querySelector('.ratings');
    const stars = rating.querySelectorAll('.stars span i');
    const label = rating.querySelector('label');
    const ratingValue = parseFloat(label.textContent);

    // 별 아이콘 초기화
    stars.forEach(star => {
        star.classList.remove('fas', 'fa-star', 'fa-star-half-alt');
        star.classList.add('far', 'fa-star');
        star.style.color = 'var(--light-gray)'; // 기본 별 색상
    });

    // 평점에 따라 별 아이콘 적용
    for (let i = 0; i < stars.length; i++) {
        if ((ratingValue - i) > 1 ) {
            stars[i].classList.remove('far', 'fa-star-half-alt');
            stars[i].classList.add('fas', 'fa-star');
            stars[i].style.color = 'var(--gold)'; // 채워진 별 색상
        }
        else if((ratingValue - i) >= 0.5){
            stars[i].classList.remove('far', 'fa-star');
            stars[i].classList.add('fas', 'fa-star-half-alt');
            stars[i].style.color = 'var(--gold)'; // 반 채워진 별 색상
            break;
        }
    }
}

/* 메뉴 공지 사항 리뷰 버튼 관련 부분 */
const buttons = document.querySelectorAll(".tab-btn");
const contents = document.querySelectorAll(".tab-content");

// 탭 버튼 클릭 이벤트 핸들러
function handleTabClick(event) {
    // 모든 버튼과 컨텐츠 초기화
    buttons.forEach(btn => {
        btn.style.backgroundColor = 'inherit';
        btn.style.color = '#717171';
    });

    contents.forEach(content => {
        content.style.display = 'none';
    });

    // 클릭된 버튼과 관련된 컨텐츠 활성화
    const selectedContentId = event.target.getAttribute('data-target');
    const selectedContent = document.querySelector(`.${selectedContentId}`);

    console.log(selectedContentId);
    event.target.style.backgroundColor = 'var(--orange)';
    event.target.style.color = 'var(--white)';
    selectedContent.style.display = 'block';
}

// 각 버튼에 이벤트 리스너 추가
buttons.forEach(button => {
    button.addEventListener('click', handleTabClick);
});


// 슬라이더 부분
const slider = document.querySelector('.category-content');
let isDown = false;
let startX;
let scrollLeft;

slider.addEventListener('mousedown', (e) => {
    isDown = true;
    slider.classList.add('active');
    startX = e.pageX - slider.offsetLeft;
    scrollLeft = slider.scrollLeft;
});

slider.addEventListener('mouseleave', () => {
    isDown = false;
    slider.classList.remove('active');
});

slider.addEventListener('mouseup', () => {
    isDown = false;
    slider.classList.remove('active');
});

slider.addEventListener('mousemove', (e) => {
    if(!isDown) return;
    e.preventDefault();
    const x = e.pageX - slider.offsetLeft;
    const walk = (x - startX) * 3; //scroll-fast
    slider.scrollLeft = scrollLeft - walk;
});

// 현재 활성화된 카테고리를 갱신하고 배경과 글씨 색 변견
const categories = document.querySelectorAll('.category');
const menuContainers = document.querySelectorAll('.menu-category');
const categoryContent = document.querySelector('.category-content');

// 카테고리 버튼 색상 변경 이벤트
function setActiveCategory() {
    let currentActiveIndex = 0;
    menuContainers.forEach((container, index) => {
        const containerTop = container.getBoundingClientRect().top;
        if (containerTop - window.innerHeight / 2 < 0) {
            currentActiveIndex = index;
        }
    });

    categories.forEach((category, index) => {
        if (index === currentActiveIndex) {
            category.classList.add('active');
            ensureCategoryVisible(category);
        } else {
            category.classList.remove('active');
        }
    });
}

// 클릭한 카테고리가 화면에 완전히 보이지 않을 경우 스크롤
function ensureCategoryVisible(category) {
    const categoryRect = category.getBoundingClientRect();
    const containerRect = categoryContent.getBoundingClientRect();

    console.log(category);
    console.log("들어는 오니");
    if (categoryRect.left < containerRect.left) {
        // 카테고리 버튼이 뷰포트 왼쪽 밖에 위치한 경우
        categoryContent.scrollLeft -= (containerRect.left - categoryRect.left) + 20; // 여백 추가
    } else if (categoryRect.right > containerRect.right) {
        // 카테고리 버튼이 뷰포트 오른쪽 밖에 위치한 경우
        categoryContent.scrollLeft += (categoryRect.right - containerRect.right) + 20; // 여백 추가
    }
}

// 카테고리 클릭 이벤트
categories.forEach(category => {
    category.addEventListener('click', () => {
        const targetId = this.getAttribute('data-targets');
        const targetElement = document.getElementById(targetId);

        if (targetElement) {
            // category-content의 높이를 가져옵니다.
            const categoryContentHeight = document.querySelector('.category-content').offsetHeight;

            // targetElement까지의 절대 위치를 계산합니다.
            const elementPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;

            // category-content의 높이만큼 위치를 조정합니다.
            const offsetPosition = elementPosition - categoryContentHeight;

            // 계산된 위치로 스크롤합니다.
            window.scrollTo({
                top: offsetPosition,
                behavior: "smooth"
            });
        }
        // 현재 클릭된 카테고리가 화면에 완전히 보이도록 스크롤 조정
        ensureCategoryVisible(this); // 여기서 this는 현재 클릭된 카테고리 요소입니다.
    });
});

// 페이지 로드 시 .category-content의 원래 위치를 계산하여 저장
const stickyElement = document.querySelector('.category-content');
const headerOffset = document.querySelector('header').offsetHeight; // 만약 header가 있다면
const originalOffsetTop = stickyElement.offsetTop - headerOffset; // header 높이를 고려한 조정값

// 스크롤 이벤트 리스너를 추가하는 새 함수
window.addEventListener('scroll', () => {
    if (window.pageYOffset >= originalOffsetTop ) {
        stickyElement.classList.add('sticky');
    } else {
        stickyElement.classList.remove('sticky');
    }
});

// 리뷰 별점 정수값을 별 개수로 표시하는
function reviewStar(){
    const reviewStars = document.querySelectorAll('.review .star');

    reviewStars.forEach(star => {
        const rating = parseInt(star.getAttribute('data-rating'));
        const totalStars = 5;
        let starsHtml = '';

        // 채워진 별 생성
        for (let i = 1; i <= rating; i++) {
            starsHtml += '<span><i class="fas fa-star"></i></span>';
        }

        // 빈 별 생성
        for (let i = rating + 1; i <= totalStars; i++) {
            starsHtml += '<span><i class="far fa-star"></i></span>';
        }

        // 별점을 HTML에 삽입
        star.innerHTML = starsHtml;
    });
}

document.addEventListener('DOMContentLoaded', () => {
    // 별포 표시 부분 함수
    ratingStar();

    // 리뷰 콘텐츠의 별점 개수 표시 하는 함수
    reviewStar();

    // 스크롤 이벤트
    window.addEventListener('scroll', setActiveCategory);

    // 초기 활성화 카테고리 설정
    setActiveCategory();
});

 

그리고 코드가 너무 길어져 modal 부분을 분리했다

modal.html

<!DOCTYPE html>
<html lang="en">
<div th:fragment="modal">
    <link href="/css/user/modal/modal.css" rel="stylesheet" type="text/css">
    <script src="/js/user/modal/modal.js" type="text/javascript" defer></script>
    <!-- 신고하기 모달 창 -->
    <div id="reportModal" class="modal report-modal">
        <div class="modal-content">
            <h2 class="modal-title">가게 신고</h2>
            <p class="modal-warning">가게 신고 시 돌이킬 수 없으며,<br>허위로 신고를 작성한 경우 삭제됩니다.</p>
            <label for="reportReason" class="modal-label">신고 사유</label>
            <textarea id="reportReason" class="modal-input"></textarea>
            <button class="modal-submit button" id="reportCall">신고하기</button>
        </div>
    </div>
    <!-- 호출하기 모달 창 -->
    <div id="callModal" class="modal call-modal">
        <div class="modal-content">
            <h2 class="modal-title">호출하기</h2>
            <form id="callForm">
                <label><input type="radio" class="check" name="callOption" value="water" checked/><label></label><span>물 주세요</span></label>
                <label><input type="radio" class="check" name="callOption" value="cup" /><label></label><span>컵 주세요</span></label>
                <label><input type="radio" class="check" name="callOption" value="towel" /><label></label><span>물수건 주세요</span></label>
                <label><input type="radio" class="check" name="callOption" value="staff" /><label></label><span>그냥 직원 오세요</span></label>
                <input type="button" class="modal-submit button" id="submitCall" value="호출하기">
            </form>
        </div>
    </div>
</div>
</html>

 

modal.css


/* 모달 창 스타일 */
.modal {
    display: none; /* 초기에는 숨김 */
    position: absolute; /* 화면 중앙에 위치 */
    z-index: 1; /* 내용 위에 표시 */
    left: 0;
    top: 0;
    width: 100%; /* 전체 너비 */
    height: 100%; /* 전체 높이 */
    overflow: auto; /* 내용이 넘칠 경우 스크롤 */
    background-color: rgb(0,0,0); /* 검은색 배경 */
    background-color: rgba(0,0,0,0.4); /* 어두운 투명도 */
}

/* 모달 내용 스타일 */
.modal-content {
    background-color: #ffffff;
    margin: 10% auto; /* 페이지 중앙에 위치 조정 */
    padding: 20px; /* 패딩 증가 */
    border-radius: 8px; /* 모서리 둥글게 처리 */
    width: 400px; /* 너비 조정 */
    max-width: 90%; /* 최대 너비 설정 */
}

.modal-title {
    font-size: var(--font-middle); /* 글씨 크기 조정 */
    color: var(--black); /* 글씨 색상 변경 */
    margin-bottom: 20px; /* 하단 여백 추가 */
}

.modal-warning {
    color: var(--red); /* 경고 문구 색상 변경 */
    font-size: 16px; /* 글씨 크기 조정 */
    margin-bottom: 20px; /* 하단 여백 추가 */
}

.modal-label {
    display: block; /* 라벨을 블록 요소로 변경 */
    margin-bottom: 10px; /* 여백 추가 */
    font-weight: bold; /* 글씨 두껍게 */
    color: var(--black); /* 글씨 색상 변경 */
}

.modal-input {
    width: 100%;
    height: 100px;
    border: 1px solid var(--light-gray);
    border-radius: 4px; /* 모서리 둥글게 처리 */
    padding: 10px; /* 내부 패딩 추가 */
    margin: 0 0 20px 0; /* 하단 여백 추가 */
}

.modal-submit {
    width: 100%;
    font-size: var(--font-content);
}

/* 호출하기 모달 */
#callForm {
    width: 100%;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}

#callForm>label {
    display: flex; /* 라벨 블록 처리 */
    align-items: center;
    margin-bottom: 10px; /* 라벨 간 간격 */
    cursor: pointer; /* 마우스 오버 시 커서 변경 */
}

#callForm>.modal-submit{
    margin-top: 20px;
}
/* 옵션 사용자 버튼 */
.check{
    display: none;
}

/* 라벨의 가상 요소를 이용해 라디오 버튼 스타일링 */
#callForm .check[type="radio"] + label::before {
    content: '\f111'; /* Font Awesome의 circle 아이콘 */
    font-family: 'Font Awesome 5 Free';
    font-weight: 500;
    color: var(--light-gray);
    font-size: var(--font-middle);
    margin-right: var(--padding);
}

/* 체크된 라디오 버튼의 스타일 */
#callForm .check[type="radio"]:checked + label::before {
    content: '\f058'; /* Font Awesome의 check-circle 아이콘 */
    color: var(--orange);
}

 

modal.js


// 신고하기 버튼 클릭 이벤트
document.querySelector('.report-button').addEventListener('click', () => {
    action();
    document.getElementById('reportModal').style.display = 'block';
});

// 신고하기 버튼 클릭 이벤트로 모달 창 닫기 (옵션)
document.querySelector('.modal-submit').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'none';
    document.body.style.overflow = ''; // 스크롤 활성화
});

// 호출하기 버튼 클릭 이벤트
document.querySelector('.call-button').addEventListener('click', () => {
    action();
    document.getElementById('callModal').style.display = 'block';
});

// 호출하기 모달에서 호출하기 버튼 클릭 이벤트
document.getElementById('submitCall').addEventListener('click', () => {
    // 실제 애플리케이션에서는 이곳에 선택된 옵션을 처리하는 로직을 구현합니다.
    // 예: 선택된 라디오 버튼의 값을 서버로 전송
    console.log('호출 옵션:', document.querySelector('input[name="callOption"]:checked').value);
    document.getElementById('callModal').style.display = 'none'; // 모달 닫기
    document.body.style.overflow = ''; // 스크롤 활성화
});

// 모달 창 밖을 클릭할 때 모달 창 닫기
window.addEventListener('click', e => {

    if (e.target.classList.contains('modal')) {
        e.target.style.display = 'none';
        document.body.style.overflow = ''; // 스크롤 활성화
    }
});

function action(){
    window.scrollTo(0, 0); // 스크롤을 맨 위로 이동
    document.body.style.overflow = 'hidden'; // 스크롤 비활성화
}

 

주말에 스프링 공부와 정보처리기사 실기 자격증 공부 예정

'공부 > Ganju' 카테고리의 다른 글

[Spring/AWS] 팀프로젝트 10일차  (0) 2024.04.09
[Spring/AWS] 팀프로젝트 9일차  (0) 2024.04.08
[Spring/AWS] 팀프로젝트 7일차  (0) 2024.04.04
[Spring/AWS] 팀프로젝트 6일차  (0) 2024.04.03
[Spring/AWS] 팀프로젝트 5일차  (0) 2024.04.02

어제 못한 유저 페이지 프론트 구현

main은 css 는 완료 했고 js도 버그를 제외한 기능 완료

info, cart, order, review의 html과 css 적용 완료

남은건 java script의 적용과 버그 수정과 더미 db 파일 적용

 

숫자 증감이나 별 클릭시 표시 

 

 

info.html - 숫자 증감과  체크 사용자 버튼 구현

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/info.css" rel="stylesheet" type="text/css">
    <script src="/js/user/info.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/info" method="post" class="info-form">
            <div class="menu-image">
                <img src="/images/sample.png" alt="음식 이미지" class="menu-img">
            </div>
            <div class="info">
                <div class="title">짜파게티</div>
                <div class="contents">맛있는 짜파게티 김치 미포함</div>
                <div class="price">300원</div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="text">매운맛</div>
                    <div class="select">필수</div>
                </div>
                <div class="contents">
                    <label class="select">
                        <input type="radio" name="spiciness" class="check" value="spicy">
                        <label></label>
                        <span class="content">맵게 해주세요</span>
                        <span class="price">+3000원</span>
                    </label>
                    <label class="select">
                        <input type="radio" name="spiciness" class="check" value="not-spicy">
                        <label></label>
                        <span class="content">안맵게 해주세요</span>
                        <span class="price">+4000원</span>
                    </label>
                </div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="text">토핑</div>
                    <div class="select">선택</div>
                </div>
                <div class="contents">
                    <label class="select">
                        <input type="checkbox" class="check">
                        <label></label>
                        <span class="content">치즈</span>
                        <span class="price">1000원</span>
                    </label>
                    <label class="select">
                        <input type="checkbox" class="check">
                        <label></label>
                        <span class="content">감자</span>
                        <span class="price">1000원</span>
                    </label>
                </div>
            </div>
            <div class="count">
                <div class="inner">
                    <div class="text">수량</div>
                    <div class="num">
                        <input type="button" class="minus" value="-">
                        <div class="text">1</div>
                        <input type="button" class="plus" value="+">
                    </div>
                </div>
                <input type="button" class="submit button" value="300원 담기" onclick="infosubmit(form)">
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

info.css

@charset "UTF-8";

.container form{
    width: 100%;
    margin: 0;
    padding: 0;
    margin-bottom: var(--fixedBtn-height);
}
/* 메인 사진 */
.container .menu-image{
    width: 100%;
    height: 250px;
    overflow: hidden;
}

.container .menu-image img{
    width: 100%;
    height: auto;
    object-fit: cover;
    object-position: center top;
}

/* 음식 설명 */
.container .info{
    padding: var(--padding);
    margin: 0;
    display: flex;
    flex-direction: column;
    position: relative;
}

.container .info .title{
    font-size: var(--font-big);
    font-weight: bold;
    margin-bottom: 10px;
    color: var(--black);
}

.container .info .contents{
    font-size: var(--font-small);
    color: var(--light-gray);
}

.container .info .price{
    font-size: var(--font-middle);
    color: var(--black);
    text-align: right;
}
/* 옵션 사용자 버튼 */
.check{
    display: none;
}
/* 라벨의 가상 요소를 이용해 체크박스 스타일링 */
.select .check[type="checkbox"] + label::before {
    content: '\f0c8'; /* Font Awesome의 square 아이콘 */
    font-family: 'Font Awesome 5 Free';
    font-weight: 500;
    color: var(--light-gray);
    font-size: var(--font-middle);
    margin-right: var(--padding);
}

/* 체크된 체크박스의 스타일 */
.select .check[type="checkbox"]:checked + label::before {
    content: '\f14a'; /* Font Awesome의 check-square 아이콘 */
    color: var(--orange);
}

/* 라벨의 가상 요소를 이용해 라디오 버튼 스타일링 */
.select .check[type="radio"] + label::before {
    content: '\f111'; /* Font Awesome의 circle 아이콘 */
    font-family: 'Font Awesome 5 Free';
    font-weight: 500;
    color: var(--light-gray);
    font-size: var(--font-middle);
    margin-right: var(--padding);
}

/* 체크된 라디오 버튼의 스타일 */
.select .check[type="radio"]:checked + label::before {
    content: '\f058'; /* Font Awesome의 check-circle 아이콘 */
    color: var(--orange);
}

/* 옵션 */
.container .option{
    margin: 0;
    display: flex;
    flex-direction: column;
}

/* 옵션 이름 부분 */
.container .option .title{
    padding: var(--padding);
    width: 100%;
    display: flex;
    justify-content: space-between;
    background-color: var(--extra-light-gray);
    color: var(--light-gray);
}

.container .option .title .text{
    padding: 10px;
    color: var(--gray);
}

.container .option .title .select{
    padding: 10px;
    background-color: var(--light-gray);
    color: var(--gray);
}

/* 옵션 내용 선택 부분 */
.container .option .contents{
    padding: var(--padding);
    width: 100%;
    display: flex;
    flex-direction: column;
}

.container .option .contents .select{
    display: flex;
    padding: 10px 0;
}

.container .option .contents .select .content{
    flex: 1;
}

/* 수량 */
.container .count {
    position: fixed;
    left: 50%;
    bottom: 0;
    margin: 0;
    /* 자신의 너비의 반만큼 왼쪽으로 이동하여 정확히 중앙에 위치하도록 조정 */
    transform: translateX(-50%);
    width: var(--innerBox-width);
    background-color: white;
    z-index: 1; /* 필요에 따라 조정 */
    padding: var(--padding);
}
.container .count .inner{
    display: flex;
    justify-content: space-between; /* 내부 요소를 양 끝으로 정렬 */
    width: 100%; /* 부모 컨테이너의 전체 너비 사용 */
    padding: 0 20px; /* 좌우 패딩 추가로 내부 요소들 사이 간격 조정 */
    margin-bottom: 10px;
}
.container .count .inner .text {
    font-size: var(--font-content); /* 필요에 따라 조정 */
    color: var(--black);
    display: flex;
    justify-content: center;
    align-items: center;
}

.container .count .inner .num {
    padding: 5px;
    display: flex;
    justify-content: right;
    border: 2px solid var(--light-gray);
}

.container .count .inner .num .minus,
.container .count .inner .num .plus{
    padding: 0 15px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 0;
    background-color: var(--white);
    font-size: var(--font-content); /* 버튼 내 텍스트 크기 */
    cursor: pointer; /* 마우스 커서 변경 */
}
.container .count .inner .num .text{
    width: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: var(--font-content); /* 필요에 따라 조정 */
}

/* 버튼 */
.container .count .submit{
    width: calc(100% - 20px); /* 패딩 고려하여 너비 조정 */
}

 

info.js

/* form 하기 전에 체크 */

function infosubmit(form) {
    form.submit();
}

// 수량 증감 액션
const minus = document.querySelector('.minus');
const plus = document.querySelector('.plus');
const text = document.querySelector('.count .inner .num .text');

// 수량 증가 함수
function plusNum() {
    let num = parseInt(text.innerHTML);
    if (num < 100) {
        num += 1;
        text.innerHTML = num;
    }
}

// 수량 감소 함수
function minusNum() {
    let num = parseInt(text.innerHTML);
    if (num > 1) {
        num -= 1;
        text.innerHTML = num;
    }
}

// 마이너스 버튼 눌렀을 때
minus.addEventListener('mousedown', e => {
    minusNum(); // 수량 감소
});

// 플러스 버튼 눌렀을 때
plus.addEventListener('mousedown', e => {
    plusNum(); // 수량 증가
});

 

cart.html - 숫자 증감을 개별적으로 구현 메뉴추가 메인 링

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>장바구니</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/cart.css" rel="stylesheet" type="text/css">
    <script src="/js/user/cart.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/order" method="post">
            <div class="restaurant-name">더조은 식당</div>
            <div class="orders">
                <div class="menu">
                    <div class="text">
                        <div class="title">짜파게티</div>
                        <div class="menu-price">가격 : 5,000원</div>
                        <div class="option">옵션 : 안맵게 해주세요(-3000원)</div>
                        <div class="total-price">3000원</div>
                    </div>
                </div>
                <div class="etc">
                    <div class="image">
                        <img src="/images/sample.png" alt="음식 이미지" class="img">
                    </div>
                    <div class="num">
                        <input type="button" class="minus" value="-">
                        <div class="text">2</div>
                        <input type="button"  class="plus" value="+">
                    </div>
                </div>
            </div>
            <div class="orders">
                <div class="menu">
                    <div class="text">
                        <div class="title">짜파게티2</div>
                        <div class="menu-price">가격 : 4,000원</div>
                        <div class="option">옵션 : 맵게 해주세요(3000원)</div>
                        <div class="total-price">10000원</div>
                    </div>
                </div>
                <div class="etc">
                    <div class="image">
                        <img src="/images/sample.png" alt="음식 이미지" class="img">
                    </div>
                    <div class="num">
                        <input type="button" class="minus" value="-">
                        <div class="text">3</div>
                        <input type="button"  class="plus" value="+">
                    </div>
                </div>
            </div>
            <div class="menu-plus">
                +메뉴추가
            </div>
            <div class="request">
                <label for="contents" class="title">요청사항</label>
                <textarea class="contents" id="contents" name="contents" placeholder="내용을 입력하세요" rows="4"></textarea>
                <input type="button" class="submit button" onclick="cartsubmit(form)" value="3000원 주문하기">
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

cart.css

@charset "UTF-8";

.container form{
    margin: 0;
    width: 100%;
    padding: 0;
}

form>div{
    margin: 0;
    padding: 20px;
}

/* 식당 이름 */
.container form .restaurant-name{
    font-size: var(--font-middle);
    font-weight: bold;
    border-top: 1px solid var(--light-gray);
}

/* 주문 리스트 */
.container form .orders{
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    border-top: 1px solid var(--light-gray);
}
.container form .orders .menu{}

.container form .orders .menu .text{
}
.container form .orders .menu .text .title,
.container form .orders .menu .text .total-price{
    font-size: var(--font-content);
    color: var(--black);
    padding-bottom: 10px;
}

.container form .orders .menu .text .menu-price,
.container form .orders .menu .text .option
{
    font-size: var(--font-small);
    color: var(--gray);
    padding-bottom: 10px;
}

.container form .orders .menu .text .total-price{
    padding-top: 10px;
}

.container form .orders .etc{
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}
.container form .orders .etc .image{
    margin-left: auto; /* 오른쪽 정렬 */
    width: 90px;
    height: 90px;
    overflow: hidden;
}

.container form .orders .etc .image img{
    width: 100%;
    height: auto;
}

.container form .orders .etc .num{
    padding: 5px;
    display: flex;
    border: 1px solid var(--light-gray);
}
.container form .orders .etc .num .minus,
.container form .orders .etc .num .plus{
    padding: 0 15px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 0;
    background-color: var(--white);
    font-size: var(--font-content); /* 버튼 내 텍스트 크기 */
    cursor: pointer; /* 마우스 커서 변경 */
}
.container form .orders .etc .num .text{
    width: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: var(--font-content); /* 필요에 따라 조정 */
}

/* 새로운 매뉴 추가 */
.container form .menu-plus{
    background-color: var(--extra-light-gray);
    text-align: center;
    color: var(--gray);
}

/* 요청사항 */
.container form .request{
    
    padding-top: 40px;
}
.container form .request .title{
    font-weight: bold;
}
.container form .request .contents{
    border: 1px solid var(--light-gray);
}
.container form .request .submit{}

 

cart.js

// form 관련
function cartsubmit(form){
    form.submit();
}

// 숫자 증감

const minus = document.querySelectorAll('.minus');
const plus = document.querySelectorAll('.plus');

// 수량 증가 함수
function plusNum(text) {
    let num = parseInt(text.innerHTML);
    if (num < 100) {
        num += 1;
        text.innerHTML = num;
    }
}

// 수량 감소 함수
function minusNum(text) {
    let num = parseInt(text.innerHTML);
    if (num > 1) {
        num -= 1;
        text.innerHTML = num;
    }
}

minus.forEach( e =>{
    e.addEventListener('mousedown', () => {
        // 'text' 요소를 이 버튼과 가장 가까운 상위 요소에서 찾습니다.
        const text = e.closest('.num').querySelector('.text');
        minusNum(text); // 수량 감소
    });
});

plus.forEach( e =>{
    e.addEventListener('mousedown', () => {
        // 'text' 요소를 이 버튼과 가장 가까운 상위 요소에서 찾습니다.
        const text = e.closest('.num').querySelector('.text');
        plusNum(text); // 수량 증가
    });
});


// 메뉴 추가 버튼 클릭 시 메인으로
const menuPlus = document.querySelector(".menu-plus");

menuPlus.addEventListener('click', () =>{
    location.href = '/user/main';
});

 

order.html - 여긴 그냥 스타일 다듬기

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="/css/style.css" type="text/css">
    <link rel="stylesheet" href="/css/manager/order.css" type="text/css">
    <script src="/js/manager/order.js" defer type="text/javascript"></script>
</head>
<body>
    <div class="innerBox">
        <header>
            <div>
                <a href=""><i class="fas fa-home"></i></a>
            </div>
            <p>주문관리</p>
            <div>
                <a href="#"></a>
            </div>
        </header>
        <div class="container">
            <ul class="order-state-summary">
                <li class="call">
                    <p class="state-name">호출</p>
                    <p class="state-content"><span>9</span>건</p>
                </li>
                <li class="wait">
                    <p class="state-name">주문</p>
                    <p class="state-content"><span>3</span>건</p>
                </li>
                <li class="okay">
                    <p class="state-name">승인</p>
                    <p class="state-content"><span>1</span>건</p>
                </li>
            </ul>
            <button onclick="location.href='/manager/sales'">총 매출 확인하기 ></button>
            <ul class="list-select">
                <li class="on">전체</li>
                <li>호출</li>
                <li>대기</li>
                <li>승인</li>
            </ul>
            <ul class="order-list">
                <li>
                    <div class="content-summary">
                        <div class="left">
                            <p class="order-title call">호출</p>
                            <p class="order-subtitle">와주세요</p>
                            <span class="table-name">1번 테이블</span>
                        </div>
                        <div class="right">
                            <span class="time">13:00</span>
                        </div>
                    </div>
                </li>
                <li>
                    <div class="content-summary">
                        <div class="left">
                            <p class="order-title wait">주문</p>
                            <span class="table-name">1번 테이블</span>
                        </div>
                        <div class="right">
                            <span class="time">13:00</span>
                            <button><i class="fas fa-chevron-down"></i></button>
                        </div>
                    </div>
                    <table class="info">
                        <tr>
                            <th>상품이름</th>
                            <th>수량</th>
                            <th>금액</th>
                        </tr>
                        <tr>
                            <td>짜파게티</td>
                            <td>2</td>
                            <td>3,000</td>
                        </tr>
                        <tr>
                            <td>토마토 파스타</td>
                            <td>23</td>
                            <td>9,000</td>
                        </tr>
                        <tr>
                            <td>총 합계</td>
                            <td>25</td>
                            <td>267,000</td>
                        </tr>
                    </table>
                </li>
            </ul>
        </div>
    </div>
</body>
</html>

 

order.css

@charset "UTF-8";

.container>div{
    margin: 0;
    padding: 20px;
    border-bottom: 1px solid var(--light-gray);
}

/* 식당 이름 */
.container .restaurant-name{
    width: 100%;
    font-size: var(--font-middle);
    font-weight: bold;
    text-align: left;
}

/* 주문 목록 */
.container .order{
    width: 100%;
    display: flex;
    justify-content: space-between;
}
.container .order .content{}

.container .order .content .title{
    font-size: var(--font-content);
    color: var(--black);
    padding-bottom: 10px;
}
.container .order .content .before-price,
.container .order .content .option{
    font-size: var(--font-small);
    color: var(--gray);
    padding-bottom: 10px;
}

.container .order .content .option{
    padding-bottom: 0;
}
.container .order .after-price{
    display: flex;
    align-items: end;
}

/* 총 금액 */
.container .total-price{
    border-bottom: 0;
    width: 100%;
    display: flex;
    justify-content: space-between;
}
.container .total-price .title{}
.container .total-price .content{}

/* 요청사항 */
.container .request{
    border-bottom: 0;
    width: 100%;
    background-color: var(--extra-light-gray);
}
.container .request .title{
    padding-bottom: 20px;
}
.container .request .content{
    font-size: var(--font-small);
    color: var(--gray);
}
/* 기타? */
.container .etc{
    border-bottom: 0;
    width: 100%;
    padding: 20px;
}
/* 경고 문구 */
.container .etc .warning{
    font-size: var(--font-small);
    color: var(--red);
    padding-bottom: 20px;
    text-align: center;
}

/* 버튼 */
.container .etc .submit{
    width: 100%;
}

 

order.js

없음 나중에 백엔드 구현할때 할듯

 

review.html - 여긴 별 클릭 했을시 값변경과 별 색 변경

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>리뷰작성</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/review.css" rel="stylesheet" type="text/css">
    <script src="/js/user/review.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/review" method="post">
            <div class="restaurant-name">더조은 식당</div>
            <div class="stars">
                <div class="star-span">
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <span><i class="fas fa-star"></i></span>
                    <input type="hidden" name="star" id="star" class="star">
                </div>
            </div>
            <div class="nickname">
                <label class="title">닉네임</label>
                <input type="text" class="contents" placeholder="닉네임을 입력해 주세요"></input>
            </div>
            <div class="review">
                <label class="title">리뷰 내용</label>
                <textarea class="contents" cols="3" rows="5"></textarea>
            </div>
            <div class="buttons">
                <input type="button" class="submit button" onclick="reviewCheck(form)" value="리뷰 작성">
            </div>
        </form>
    </div>
    <footer></footer>
</div>

</body>
</html>

 

review.css

@charset "UTF-8";

.container form{
    margin: 0;
    padding: 0;
}

form{
    width: 100%;
}

form>div{
    margin: 0;
    padding: 20px;
}

/* 식당 이름 */
.container form .restaurant-name{
    width: 100%;
    font-size: var(--font-middle);
    font-weight: bold;
    text-align: left;
}

/* 별 */
.container form .stars{
    width: 100%;
    padding: 0 20px;
}

.container form .stars .star-span{
    font-size: var(--font-big);
    color: var(--light-gray);
    cursor: default;
}

.container form .stars .star-span span{
    margin: -2px;
    cursor: pointer;
}

/* 닉네임 */
.container form .nickname{
    width: 100%;
}

.container form .nickname .title{
    margin-bottom: 10px;
}

.container form .nickname .contents{
    border: 1px solid var(--light-gray);
}

/* 리뷰 내용 */
.container form .review{
    width: 100%;
}

.container form .review .title{
    margin-bottom: 10px;
}

.container form .review .contents{
    margin: 0;
    border: 1px solid var(--light-gray);
}

/* 버튼 */
.container form .buttons{
    padding-top: 0;
}
.container form .buttons .submit{
    font-size: var(--font-middle);
}

 

review.js

function reviewCheck(form){
    form.submit();
}

// 별 클릭시 색 변하는 이벤트
const stars = document.querySelectorAll('.star-span span');
const text = document.querySelector('.star-span .star');

stars.forEach( (e, idx) =>{
    e.addEventListener('click', () =>{
        updateStars(idx);
    })
});

// 별을 i 번째 만큼 색 바꿈
function updateStars(idx){
    stars.forEach( (e, i) =>{
        text.value = idx+1;
        if(i <= idx){
            e.style.color = '#FFBE3F';
        }else{
            e.style.color = '#c2c2c2';
        }
    });
}

 

main구현할때 gpt 한테 다 해달래 했더니 너무 복잡해짐 
나머진 최대한 혼자서 해서 수정이 쉬웠는데
편한데 수정이 어렵다는 단점이 있음

'공부 > Ganju' 카테고리의 다른 글

[Spring/AWS] 팀프로젝트 9일차  (0) 2024.04.08
[Spring/AWS] 팀프로젝트 8일차  (0) 2024.04.06
[Spring/AWS] 팀프로젝트 6일차  (0) 2024.04.03
[Spring/AWS] 팀프로젝트 5일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 4일차  (0) 2024.04.02

오늘은 어제에 이어 페이지 연결과 html css javascript 위주로 진행

 

user가 볼수 있는 main 페이지와 info, cart, order, review의 html 태그와 css 정도는 마무리 하고

main 자바 스크립트나 위치 스타일 마무리
info 페이지도 cs와 js 마무리

main, info는 thymeleaf 받을 태그 정도는 만들어 두고 나중에 controller 쪽 만지면 완료

 

 

오늘 배운것 css에서 gap과 sticky, script에서 DOMContentLoaded, getBoundingClientRect()

 

gap: 10px; 이건 각 항목의 간격을 고르게 주더라고 가운데는 벌어지는데 그걸 일정하게 해줌

 

position: sticky; 이건 해당 영역을 벗어나면 따라오게 해주는 신기한 명령

 

DOMContentLoaded, 페이지에서 무든 이벤트가 있을 때마다 감지 이걸 많이 쓰면 렉걸리려나

 

getBoundingClientRect, 스크롤 감지 관련 문제 때문에 GPT에게 물어가면서 함 GPT 만세

 

오늘 수정한 코드

 

main.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8" />
    <title></title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/main.css" rel="stylesheet" type="text/css">
    <script src="/js/user/main.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <div class="restaurant-image">
            <img src="/images/sample.png" alt="식당 메인 이미지" class="restaurant-img">
        </div>
        <div class="title">
            <div class="restaurant-name">더조은 식당</div>
            <button class="report-button button">
                <i class="fas fa-exclamation-triangle"></i>신고하기
            </button>
        </div>
        <div class="ratings-reviews">
            <div class="ratings">
                <div class="stars">★★★★★</div>  <!-- 5개의 별을 기본으로 설정 -->
                <label>3.3</label> <!-- 예시 평점 -->
            </div>
            <button class="call-button button">호출하기</button>
        </div>
        <div class="buttons">
            <button class="menu-btn button">메뉴</button>
            <button class="announcement-btn button">공지사항</button>
            <button class="reviews-btn button">리뷰</button>
        </div>
        <div class="content">
            <div class="menu-content">
                <div class="category-content">
                    <div th:each="category : ${categories}" class="category" th:text="${category.name}" th:data-target="'menu-category-' + ${category.id}"></div>
                </div>
                <div class="menus-container">
                    <div th:each="category : ${categories}" class="menu-category"  th:id="'menu-category-' + ${category.id}" th:data-category-id="${category.id}" >
                        <div th:text="${category.name}" class="category-name"></div>
                        <div class="menus" th:each="menu : ${menus}">
                            <div th:if="${menu.category.id} == ${category.id}" class="menu" th:onclick="'location.href=\'/user/info?id=' + ${menu.id} + '\''">
                                <div class="image">
                                    <img src="/images/sample.png" alt="메뉴 이미지" class="restaurant-image">
                                </div>
                                <div class="text">
                                    <div th:text="${menu.name}" class="menu-name"></div>
                                    <!--th:text="${menu.info}"-->
                                    <div class="menu-info">맛있음</div>
                                    <div th:text="${menu.price + '원'}" class="menu-price"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="announcement-content">
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항1</div>
                        <div class="date">16:12</div>
                    </div>
                    <div class="content">쉬고싶어서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항2</div>
                        <div class="date">2024.04.03</div>
                    </div>
                    <div class="content">아파서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항3</div>
                        <div class="date">2024.04.02</div>
                    </div>
                    <div class="content">싱숭생숭해서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항4</div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">해어져서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">
                        <div class="text">공지사항5</div>
                        <div class="date">2024.03.20</div>
                    </div>
                    <div class="content">그냥 쉽니다</div>
                </div>
            </div>
            <div class="review-content" >
                <div class="review">
                    <div class="title">박윤재</div>
                    <div class="mid">
                        <!-- 여기에 별점을 표시할 div를 추가합니다. 데이터 속성(data-rating)을 사용하여 별점을 지정합니다. -->
                        <div class="star" data-rating="5"></div>
                        <div class="date">1달전</div>
                    </div>
                    <div class="content">음식이 친절하고 사장님이 맛있어요</div>
                </div>
                <div class="review">
                    <div class="title">손지영</div>
                    <div class="mid">
                        <div class="star" data-rating="4"></div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">1주전</div>
                </div>
                <div class="review">
                    <div class="title">윤경재</div>
                    <div class="mid">
                        <div class="star" data-rating="1"></div>
                        <div class="date">2시간전</div>
                    </div>
                    <div class="content">집에 보내주세요</div>
                </div>
            </div>
        </div>
    </div>
    <footer></footer>
    <!-- 신고하기 모달 창 -->
    <div id="reportModal" class="modal report-modal">
        <div class="modal-content">
            <h2 class="modal-title">가게 신고</h2>
            <p class="modal-warning">가게 신고 시 돌이킬 수 없으며,<br>허위로 신고를 작성한 경우<br>신고는 무효화 됩니다.</p>
            <label for="reportReason" class="modal-label">신고 사유</label>
            <textarea id="reportReason" class="modal-input"></textarea>
            <button class="modal-submit button" id="reportCall">신고하기</button>
        </div>
    </div>
    <!-- 호출하기 모달 창 -->
    <div id="callModal" class="modal call-modal">
        <div class="modal-content">
            <h2 class="modal-title">호출하기</h2>
            <form id="callForm">
                <label><input type="radio" name="callOption" value="water" checked/> 물 주세요</label><br />
                <label><input type="radio" name="callOption" value="cup" /> 컵 주세요</label><br />
                <label><input type="radio" name="callOption" value="towel" /> 물수건 주세요</label><br />
                <label><input type="radio" name="callOption" value="staff" /> 그냥 직원오세요</label>
            </form>
            <button class="modal-submit button" id="submitCall">호출하기</button>
        </div>
    </div>
</div>
</body>
</html>

 

main.css

@charset "UTF-8";

/* 메인 이미지 */
.container .restaurant-image{
    width: 100%;
    height: 200px;
    overflow: hidden;
}

.container .restaurant-image img{
    width: 100%;
    height: auto;
    object-fit: cover;
    object-position: center top;
}

/* 타이틀과 신고버튼 */
.container .title{
    width: 100%;
    padding: var(--padding);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* 타이틀 */
.container .title .restaurant-name{
    font-size: var(--font-big);
    font-weight: bold;
}

/* 신고 */
.container .title .report-button{
    padding: 0;
    background-color: transparent;
    color: red;
    border: none;
    font-size: var(--font-small);
}

.container .title .report-button .fa-exclamation-triangle{
    margin-right: 5px;
}

/* 별표와 리뷰 버튼*/
.container .ratings-reviews{
    margin-bottom: 10px;
    padding: var(--padding);
    padding-top: 0;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* 별표 부분*/
.container .ratings-reviews .ratings {
    display: flex;
    align-items: center;
}

/* 별표의 별 부분 */
.container .ratings-reviews .ratings .stars{
    position: relative;
    display: inline-block;
    font-size: 20px; /* 별 크기 조정 */
    direction: rtl; /* 별을 오른쪽에서 왼쪽으로 채워나가기 위해 */
}

.container .ratings-reviews .ratings .stars::after{
    content: "★★★★★";
    color: lightgray; /* 기본 별 색상 설정 */
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 0;
 }

.star-fill {
    display: block;
    color: gold; /* 금색 별 색상 설정 */
    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    z-index: 1;
    direction: ltr; /* 금색 별이 왼쪽에서 시작하도록 설정 */
}

/* 별표의 숫자 부분 */
.container .ratings-reviews .ratings label{
    margin-left: 10px;
}

/* 호출 버튼 부분*/
.container .ratings-reviews .call-button{
    border: 0;
    border-radius: 0;
    padding: 10px 30px;
}


/* 메뉴 공지사항 리뷰 */
.container .buttons{
    border-bottom: 3px solid var(--orange);
    display: flex;
    width: 100%;
}

/* 메뉴 */
.container .buttons .menu-btn{
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 공지사항 */
.container .buttons .announcement-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 리뷰 */
.container .buttons .reviews-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
    font-size: var(--font-content);
    padding: var(--padding);
}

/* 버튼을 눌렀을때 보여주는 콘텐츠 영역 */
.container .content{
    width: 100%;
}

/* 메뉴 콘텐츠 */
.container .content .menu-content{
    width: 100%;
}

/* 카테고리 슬라이더 부분 */
.container .content .menu-content .category-content{
    width: 100%;
    display: flex; /* 이 부분을 추가합니다 */
    overflow-x: auto; /* 수평 스크롤을 위해 추가합니다 */
    background-color: var(--white);
    scrollbar-width: none;
    position: sticky;
    top: 0; /* 상단에 고정 */
    z-index: 1; /* 다른 요소들 위에 오도록 z-index 설정 */
}

.container .content .menu-content .category-content .category{
    margin: 10px;
    padding: 10px 20px;
    flex: 0 0 auto; /* flex 항목이 자신의 크기를 유지하도록 설정합니다 */
    border-radius: 20px;
    text-align: center;
    background-color: var(--white);
    color:var(--black);
    cursor: pointer; /* 마우스 오버 시 커서 변경을 위해 추가합니다 */
    border: 1px solid var(--gray);
    transition: background-color 0.3s, color 0.3s; /* 부드러운 색상 전환 */
}

.container .content .menu-content .category-content .category.active{
    background-color: var(--gray);
    color: var(--white);
}

.container .content .menu-content .category-content:last-child{
    margin-right: 0;
}

/* 메뉴 리스트 */
.container .content .menu-content .menus-container {
}
.container .content .menu-content .menus-container .category-name{
    padding:var(--padding);
    background-color: var(--extra-light-gray);
    color: var(--gray);
    font-size: var(--font-middle);
    font-weight: bold;
    border-bottom: 2px solid var(--light-gray);
}
.container .content .menu-content .menus-container .menu{
    width: 100%;
    display: flex;
    border-bottom: 2px solid var(--light-gray);
}
.container .content .menu-content .menus-container .menu .image{
    padding: 10px;
    width: 120px;
    height: 120px;
    overflow: hidden;
}
.container .content .menu-content .menus-container .menu .image img{
    width: 100%;
    height: 100%;
}
.container .content .menu-content .menus-container .menu .text{
    padding: 10px;
    display: flex;
    flex-direction: column;
}
.container .content .menu-content .menus-container .menu .text .menu-name{
    margin-bottom: 10px;
    font-size: var(--font-content);
    font-weight: bold;
    color:var(--black);
}

.container .content .menu-content .menus-container .menu .text .menu-info{
    margin-bottom: 10px;
    flex: 1;
    font-size: var(--font-content);
    color:var(--gray);
}

.container .content .menu-content .menus-container .menu .text .menu-price{
    font-size: var(--font-content);
    color:var(--black);
}

/* 공지사항 콘텐츠 */
.container .content .announcement-content{
    width: 100%;
    display: none;
}

.container .content .announcement-content .announcement {
    width: 100%;
    display: flex;
    flex-direction: column;
    padding: 20px;
    border-bottom: 2px solid var(--light-gray);
}

.container .content .announcement-content .announcement .title {
    padding:0;
    font-size: var(--font-middle);
    color: var(--black);
    margin-bottom: 20px;
}
.container .content .announcement-content .announcement .date{
    padding:0;
    font-size: var(--font-small);
    color: var(--light-gray);
}
.container .content .announcement-content .announcement .content{
    padding:0;
    font-size: var(--font-content);
    color: var(--gray);
}

/* 리뷰 */
.container .content .review-content{
    width: 100%;
    display: none;
    padding: var(--padding);
}

.container .content .review-content .review{
    margin-bottom: 20px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    border: 2px solid var(--light-gray);
    padding: var(--padding);
}
.container .content .review-content .review .title{
    padding: 0;
    font-size: var(--font-content);
    color: var(--black);
}
.container .content .review-content .review .mid{
    display: flex;
    font-size: var(--font-small);
    color: var(--light-gray);
    flex: 1;
    padding-bottom: 20px;
}

.container .content .review-content .review .mid .star{
    font-size: var(--font-small); /* 별의 크기 조절 */
    color: #ccc; /* 기본 별 색상 (회색) */
    position: relative; /* 상대적 위치 설정 */
    margin-right: 20px;
}

.container .content .review-content .review .mid .star span.filled {
    color: gold; /* 노란색 별 */
}

.container .content .review-content .review .mid .star span.empty {
    color: #ccc; /* 회색 별 */
}

.container .content .review-content .review .mid .date{}
.container .content .review-content .review .content{
    font-size: var(--font-content);
    color: var(--black);
}

/* 모달 창 스타일 */
.modal {
    display: none; /* 초기에는 숨김 */
    position: absolute; /* 화면 중앙에 위치 */
    z-index: 1; /* 내용 위에 표시 */
    left: 0;
    top: 0;
    width: 100%; /* 전체 너비 */
    height: 100%; /* 전체 높이 */
    overflow: auto; /* 내용이 넘칠 경우 스크롤 */
    background-color: rgb(0,0,0); /* 검은색 배경 */
    background-color: rgba(0,0,0,0.4); /* 어두운 투명도 */
}

/* 모달 내용 스타일 */
.modal-content {
    background-color: #fefefe;
    margin: 15% auto; /* 페이지 중앙에 위치 */
    padding: 20px;
    border: 1px solid #888;
    width: 300px; /* 너비 설정 */
    height: 300px; /* 높이 설정 */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.modal-title {
    text-align: center;
    font-weight: bold;
}

.modal-warning {
    color: red;
    text-align: center;
}

.modal-label {
    text-align: left;
}

.modal-input {
    width: 100%;
    height: 100px;
}

.modal-submit {
    background-color: #222; /* 검은색 배경 */
    color: #fff; /* 하얀색 글씨 */
    text-align: center;
}

 

main.js - 여기서 별 구현은 GPT의 힘을 많이 빌림

const menu = document.querySelector(".menu-btn");
const announcement = document.querySelector(".announcement-btn");
const reviews = document.querySelector(".reviews-btn");
const menuContent = document.querySelector('.menu-content');
const announcementContent = document.querySelector('.announcement-content');
const reviewContent = document.querySelector('.review-content');

/* 식당 평점 별 부분*/
document.addEventListener('DOMContentLoaded', function() {
    var ratings = document.querySelectorAll('.ratings');

    ratings.forEach(function(rating) {
        var stars = rating.querySelector('.stars');
        var label = rating.querySelector('label');
        var ratingValue = parseFloat(label.textContent);
        var starPercentage = (ratingValue / 5) * 100; // 평점을 백분율로 변환
        var starPercentageRounded = `${parseFloat(starPercentage.toFixed(1))}%`; // 첫 번째 소수점 자리까지 표시

        // 금색 별을 표시할 span 요소 생성 및 스타일 설정
        var starFill = document.createElement('span');
        starFill.className = 'star-fill';
        starFill.style.width = starPercentageRounded;
        starFill.innerHTML = '★★★★★'; // 금색 별

        // 기존 별점 요소를 클리어하고, 금색 별 span과 기본 별을 추가
        stars.innerHTML = ''; // 기존 내용을 클리어
        stars.appendChild(starFill); // 금색 별 추가
        stars.innerHTML += '★★★★★'; // 기본 별 추가 (회색 별)
    });
});

/* 메뉴 버튼 클릭 시 */
menu.addEventListener('click', () => {
    menu.style.backgroundColor = '#ff7a2f';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#fff';
    announcement.style.color = '#717171';
    reviews.style.color = '#717171';

    menuContent.style.display = 'block';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'none';
});

/* 공지사항 버튼 클릭 시 */
announcement.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = '#ff7a2f';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#717171';
    announcement.style.color = '#fff';
    reviews.style.color = '#717171';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'block';
    reviewContent.style.display = 'none';
});


/* 리뷰 버튼 클릭 시 */
reviews.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = '#ff7a2f';

    menu.style.color = '#717171';
    announcement.style.color = '#717171';
    reviews.style.color = '#fff';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'block';
});

// 신고 버튼 클릭 이벤트
document.querySelector('.report-button').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'block';
});

// 신고하기 버튼 클릭 이벤트로 모달 창 닫기 (옵션)
document.querySelector('.modal-submit').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'none';
});

// 호출하기 버튼 클릭 이벤트
document.querySelector('.call-button').addEventListener('click', () => {
    document.getElementById('callModal').style.display = 'block';
});

// 호출하기 모달에서 호출하기 버튼 클릭 이벤트
document.getElementById('submitCall').addEventListener('click', () => {
    // 실제 애플리케이션에서는 이곳에 선택된 옵션을 처리하는 로직을 구현합니다.
    // 예: 선택된 라디오 버튼의 값을 서버로 전송
    console.log('호출 옵션:', document.querySelector('input[name="callOption"]:checked').value);
    document.getElementById('callModal').style.display = 'none'; // 모달 닫기
});

// 모달 창 밖을 클릭할 때 모달 창 닫기
window.addEventListener('click', e => {

    if (e.target.classList.contains('modal')) {
        e.target.style.display = 'none';
    }
});


// 슬라이더 부분
document.addEventListener('DOMContentLoaded', function() {
    const slider = document.querySelector('.category-content');
    let isDown = false;
    let startX;
    let scrollLeft;

    slider.addEventListener('mousedown', (e) => {
        isDown = true;
        slider.classList.add('active');
        startX = e.pageX - slider.offsetLeft;
        scrollLeft = slider.scrollLeft;
    });

    slider.addEventListener('mouseleave', () => {
        isDown = false;
        slider.classList.remove('active');
    });

    slider.addEventListener('mouseup', () => {
        isDown = false;
        slider.classList.remove('active');
    });

    slider.addEventListener('mousemove', (e) => {
        if(!isDown) return;
        e.preventDefault();
        const x = e.pageX - slider.offsetLeft;
        const walk = (x - startX) * 3; //scroll-fast
        slider.scrollLeft = scrollLeft - walk;
    });
});

document.addEventListener('DOMContentLoaded', function() {
    const categories = document.querySelectorAll('.category');
    const menuContainers = document.querySelectorAll('.menu-category');
    const categoryContent = document.querySelector('.category-content');

    // 현재 활성화된 카테고리를 표시하고 스크롤 시 갱신
    function setActiveCategory() {
        let currentActiveIndex = 0;
        menuContainers.forEach((container, index) => {
            const containerTop = container.getBoundingClientRect().top;
            if (containerTop - window.innerHeight / 2 < 0) {
                currentActiveIndex = index;
            }
        });

        categories.forEach((category, index) => {
            if (index === currentActiveIndex) {
                category.classList.add('active');
            } else {
                category.classList.remove('active');
            }
        });
    }

    // 클릭한 카테고리가 화면에 완전히 보이지 않을 경우 스크롤
    function ensureCategoryVisible(category) {
        const categoryRect = category.getBoundingClientRect();
        const containerRect = categoryContent.getBoundingClientRect();

        if (categoryRect.left < containerRect.left) {
            // 카테고리 버튼이 뷰포트 왼쪽 밖에 위치한 경우
            categoryContent.scrollLeft -= (containerRect.left - categoryRect.left) + 20; // 여백 추가
        } else if (categoryRect.right > containerRect.right) {
            // 카테고리 버튼이 뷰포트 오른쪽 밖에 위치한 경우
            categoryContent.scrollLeft += (categoryRect.right - containerRect.right) + 20; // 여백 추가
        }
    }

    // 카테고리 클릭 이벤트
    categories.forEach(category => {
        category.addEventListener('click', function() {
            const targetId = this.getAttribute('data-target');
            const targetElement = document.getElementById(targetId);

            if (targetElement) {
                // category-content의 높이를 가져옵니다.
                const categoryContentHeight = document.querySelector('.category-content').offsetHeight;

                // targetElement까지의 절대 위치를 계산합니다.
                const elementPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;

                // category-content의 높이만큼 위치를 조정합니다.
                const offsetPosition = elementPosition - categoryContentHeight;

                // 계산된 위치로 스크롤합니다.
                window.scrollTo({
                    top: offsetPosition,
                    behavior: "smooth"
                });
            }
        });
    });

    // 스크롤 이벤트
    window.addEventListener('scroll', setActiveCategory);

    // 초기 활성화 카테고리 설정
    setActiveCategory();
});


// 리뷰 별점 정수값을 별 개수로 표시하는
document.addEventListener('DOMContentLoaded', function() {
    const reviewStars = document.querySelectorAll('.review .star');

    reviewStars.forEach(function(star) {
        const rating = parseInt(star.getAttribute('data-rating'));
        let starsText = '★★★★★';

        // 노란색 별을 표시할 부분과 회색 별을 표시할 부분을 결정
        let filledStars = starsText.slice(0, rating).replace(/★/g, '<span class="filled">★</span>');
        let emptyStars = starsText.slice(rating).replace(/★/g, '<span class="empty">★</span>');

        // 별점을 HTML로 설정
        star.innerHTML = filledStars + emptyStars;
    });
});

 

info.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/info.css" rel="stylesheet" type="text/css">
    <script src="/js/user/info.js" type="text/javascript" defer></script>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/user/info" method="post" class="info-form">
            <div class="menu-image">
                <img src="/images/sample.png" alt="음식 이미지" class="menu-img">
            </div>
            <div class="info">
                <div class="info-title">짜파게티</div>
                <div class="info-contents">맛있는 짜파게티 김치 미포함</div>
                <div class="info-price">300원</div>
            </div>
            <div class="option">
                <div class="option-title">
                    <div class="option-title-text">매운맛</div>
                    <div class="option-title-select">필수</div>
                </div>
                <div class="option-contents">
                    <div class="option-contents-select">
                        <input type="radio" name="spiciness" class="option-check" value="spicy">
                        <span class="option-contents-content">맵게 해주세요</span>
                        <span class="option-contents-price">+3000원</span>
                    </div>
                    <div class="option-contents-select">
                        <input type="radio" name="spiciness" class="option-check" value="not-spicy">
                        <span class="option-contents-content">안맵게 해주세요</span>
                        <span class="option-contents-price">+4000원</span>
                    </div>
                </div>
            </div>
            <div class="option">
                <div class="option-title">
                    <div class="option-title-text">토핑</div>
                    <div class="option-title-select">선택</div>
                </div>
                <div class="option-contents">
                    <div class="option-contents-select">
                        <input type="checkbox" class="option-check">
                        <span class="option-contents-content">치즈</span>
                        <span class="option-contents-price">1000원</span>
                    </div>
                    <div class="option-contents-select">
                        <input type="checkbox" class="option-check">
                        <span class="option-contents-content">감자</span>
                        <span class="option-contents-price">1000원</span>
                    </div>
                </div>
            </div>
            <div class="white-gap"></div>
            <div class="count">
                <div class="count-inner">
                    <div class="count-text">수량</div>
                    <div class="count-button">
                        <input type="button" class="minus" value="-">
                        <div class="text">1</div>개
                        <input type="button"  class="plus" value="+">
                    </div>
                </div>
                <input type="button" class="submit button" value="300원 담기" onclick="infosubmit(form)">
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

info.css

@charset "UTF-8";

.container form{
    width: 100%;
    margin: 0;
    padding: 0;
}
/* 메인 사진 */
.container .menu-image{
    width: 100%;
    height: 250px;
    overflow: hidden;
}

.container .menu-image img{
    width: 100%;
    height: auto;
    object-fit: cover;
    object-position: center top;
}

/* 음식 설명 */
.container .info{
    padding: var(--padding);
    margin: 0;
    display: flex;
    flex-direction: column;
    position: relative;
}

.container .info .info-title{
    font-size: var(--font-big);
    font-weight: bold;
    margin-bottom: 10px;
    color: var(--black);
}

.container .info .info-contents{
    font-size: var(--font-small);
    color: var(--light-gray);
}

.container .info .info-price{
    font-size: var(--font-middle);
    color: var(--black);
    text-align: right;
}

/* 옵션 */
.container .option{
    margin: 0;
    display: flex;
    flex-direction: column;
}

.container .option .option-title{
    padding: var(--padding);
    width: 100%;
    display: flex;
    justify-content: space-between;
    background-color: var(--extra-light-gray);
    color: var(--light-gray);
}

.container .option .option-title .option-title-text{
    padding: 10px;
    color: var(--gray);
}

.container .option .option-title .option-title-select{
    padding: 10px;
    background-color: var(--light-gray);
    color: var(--gray);
}

.container .option .option-title .option-title-select .option-check{
    margin-right: 10px;
}

.container .option .option-title .option-title-select .option-contents-content{
    margin: 10px 0;
}

.container .option .option-contents{
    padding: var(--padding);
    width: 100%;
    display: flex;
    flex-direction: column;
}

.container .option .option-contents .option-contents-select{
    display: flex;
    margin-bottom: 10px; /* 마지막 요소를 제외하고 각 요소의 하단에 간격 추가 */
}

.container .option .option-contents .option-contents-select:last-child {
    margin-bottom: 0; /* 마지막 요소는 하단 간격 제거 */
}

.container .option .option-contents .option-contents-select .option-contents-content{
    flex: 1;
}

/* count 때문에 넣는 빈공간 */
.container .white-gap{
    width: 100%;
    height: var(--fixedBtn-height);
}

/* 수량 */
.container .count {
    position: fixed;
    left: 50%;
    bottom: 0;
    margin: 0;
    /* 자신의 너비의 반만큼 왼쪽으로 이동하여 정확히 중앙에 위치하도록 조정 */
    transform: translateX(-50%);
    width: var(--innerBox-width);
    background-color: white;
    z-index: 1; /* 필요에 따라 조정 */
    padding: var(--padding);
}
.container .count .count-inner{
    display: flex;
    justify-content: space-between; /* 내부 요소를 양 끝으로 정렬 */
    width: 100%; /* 부모 컨테이너의 전체 너비 사용 */
    padding: 0 20px; /* 좌우 패딩 추가로 내부 요소들 사이 간격 조정 */
    margin-bottom: 10px;
}
.container .count .count-inner .count-text {
    font-size: var(--font-content); /* 필요에 따라 조정 */
    color: var(--black);
    display: flex;
    justify-content: center;
    align-items: center;
}

.container .count .count-inner .count-button {
    padding: 5px;
    display: flex;
    justify-content: right;
    border: 2px solid var(--light-gray);
}

.container .count .count-inner .count-button .minus,
.container .count .count-inner .count-button .plus{
    padding: 0 15px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 0;
    background-color: var(--white);
    font-size: var(--font-content); /* 버튼 내 텍스트 크기 */
    cursor: pointer; /* 마우스 커서 변경 */
}
.container .count .count-inner .count-button .text{
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: var(--font-content); /* 필요에 따라 조정 */
}

/* 버튼 */
.container .count .submit{
    width: calc(100% - 20px); /* 패딩 고려하여 너비 조정 */
}

info.js

/* form 하기 전에 체크 */

function infosubmit(form) {
    form.submit();
}

const ocs = document.querySelectorAll('.option-contents-select');

ocs.forEach(select => {
    select.addEventListener('click', e => {
        // 'this' 대신 'select' 사용
        const input = select.querySelector('input[type=checkbox], input[type=radio]');
        if (e.target !== input) {
            input.checked = !input.checked;
            // 라디오 버튼의 경우, 다른 라디오 버튼의 상태 변경을 위해 이벤트를 발생시킵니다.
            input.dispatchEvent(new Event('change'));
        }
    });
});

 

이제 메인은 반응형 하고 값 제대로 안들어갈때나 건드릴듯 

기능 참 많네 

'공부 > Ganju' 카테고리의 다른 글

[Spring/AWS] 팀프로젝트 8일차  (0) 2024.04.06
[Spring/AWS] 팀프로젝트 7일차  (0) 2024.04.04
[Spring/AWS] 팀프로젝트 5일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 4일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 3일차  (0) 2024.03.29

오늘은 이번주 금요일 중간 발표를 위해 보여지는 페이지를 먼저 구현하기로 했습니다.

 

손님이 처음 보는 메뉴 메인 페이지 구현

상단에 가게 메인 이미지

두번째 타이틀 신고 버튼

세번째 평점 평균, 리뷰

네번재 호출하기 버튼

다섯번째 메뉴 공지 리뷰 버튼 해당 버튼을 클릭하면 여섯번째에 거기에 해당하는 컨텐츠를 보여준다

메뉴 버튼을 클락하면 나오는 메뉴 컨텐츠의 첫번째는 카테고리를 보여주고 두번째는 메뉴를 카테고리로 분류해서 세로로 보여준다

보여주기 전에 더미 db 파일이 필요하다

package kr.ganjuproject.repository.dumi;

import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import kr.ganjuproject.entity.Category;
import kr.ganjuproject.entity.Menu;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;

@Profile("local")
@Component
@RequiredArgsConstructor
public class InitMenuDB {
    private final InitMenuService initMenuService;
    @PostConstruct  // 생성자 실행되면 바로 호출
    public void init() {
        initMenuService.init();
    }
    @Component
    static class InitMenuService {
        @Autowired
        EntityManager em;
        @Transactional
        public void init() {
            // 카테고리 데이터 생성
            String[] categoryNames = {"한식", "중식", "일식", "양식", "분식", "디저트"};
            for (int i = 0; i < categoryNames.length; i++) {
                Category category = new Category(null, i + 1, categoryNames[i], new ArrayList<>());
                em.persist(category);

                // 각 카테고리에 메뉴 추가
                for (int j = 0; j < 5; j++) {
                    Menu menu = new Menu();
                    menu.setName(category.getName() + " 메뉴 " + (j + 1));
                    menu.setPrice((j + 1) * 1000); // 가격 설정 예시
                    menu.setCategory(category); // 생성된 카테고리에 메뉴 설정
                    // 메뉴에 대한 추가적인 설정...

                    em.persist(menu);

                    // 카테고리 객체에 메뉴 객체 연결 (양방향 관계 설정)
                    category.getMenus().add(menu);
                }
            }
            System.out.println("더미 데이터 생성 완료");
        }
    }
}

이렇게 서버를 시작할때 더미데이터를 만드려면

@Profile("local")

이 부분과 yml 파일에

spring:
  output:
    ansi:
      enabled: always
  profiles:
    active: local

active 를 지정해줘야 한다

어쩌다보니 기능까지 같이 구현해 버림

메인 더미데이터로 카테고리 슬라이더 메뉴 펼치기 공지사항 더미 리뷰 더미

그리고 메뉴 클릭시 페이지링크 까지 구현

오늘 작성한 코드 변경시 참고하기위해 남깁니다

 

main.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/main.css" rel="stylesheet" type="text/css">
    <script src="/js/user/main.js" type="text/javascript" defer></script>

    <!-- 슬릭 슬라이더 관련 -->
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>

    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css" />
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css" />
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <img src="/images/sample.png" alt="식당 메인 이미지" class="restaurant-image">
        <div class="title">
            <div class="restaurant-name">식당 이름</div>
            <button class="report-button button">신고</button>
        </div>
        <div class="ratings-reviews">
            <div class="ratings">
                <div class="stars">★★★★☆</div>
                <label>0.0</label>
            </div>
            <button class="review-button button">리뷰</button>
        </div>
        <div class="call">
            <button class="call-button button">호출하기</button>
        </div>
        <div class="buttons">
            <button class="menu-btn button">메뉴</button>
            <button class="announcement-btn button">공지사항</button>
            <button class="reviews-btn button">리뷰</button>
        </div>
        <div class="content">
            <div class="menu-content">
                <div class="category-content">
                    <div th:each="category : ${categories}" class="category">
                        <div th:text="${category.name}" class="category-name"></div>
                    </div>
                </div>
                <div class="menus-container">
                    <div th:each="category : ${categories}" class="menu-category" th:data-category-id="${category.id}">
                        <div th:text="${category.name}" class="category-name"></div>
                        <div class="menus" th:each="menu : ${menus}">
                            <div th:if="${menu.category.id} == ${category.id}" class="menu" onclick="location.href='/menu/info?id=${menu.id})}'">
                                <div class="image"><img src="/images/sample.png" alt="메뉴 이미지" class="restaurant-image"></div>
                                <div class="text">
                                    <div th:text="${menu.name}" class="menu-name"></div>
                                    <div th:text="${menu.info}" class="menu-info"></div>
                                    <div th:text="${menu.price}" class="menu-price"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="announcement-content">
                <div class="announcement">
                    <div class="title">공지사항1</div>
                    <div class="date">2024.04.01</div>
                    <div class="content">쉬고싶어서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">공지사항2</div>
                    <div class="date">2024.04.02</div>
                    <div class="content">여행 가고싶어서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">공지사항3</div>
                    <div class="date">2024.04.03</div>
                    <div class="content">아플것 같아서 쉽니다</div>
                </div>
                <div class="announcement">
                    <div class="title">공지사항4</div>
                    <div class="date">2024.04.04</div>
                    <div class="content">그냥 쉽니다</div>
                </div>
            </div>
            <div class="review-content" >
                <div class="review">
                    <div class="title">박윤재</div>
                    <div class="mid">
                        <div class="star">★★★★★</div>
                        <div class="date">16:00</div>
                    </div>
                    <div class="content">음식이 친절하고 사장님이 맛있어요</div>
                </div>
                <div class="review">
                    <div class="title">손지영</div>
                    <div class="mid">
                        <div class="star">★★★☆☆</div>
                        <div class="date">2024.04.01</div>
                    </div>
                    <div class="content">음 괜찮네요</div>
                </div>
                <div class="review">
                    <div class="title">윤경재</div>
                    <div class="mid">
                        <div class="star">☆☆☆☆☆</div>
                        <div class="date">2023.12.31</div>
                    </div>
                    <div class="content">집에 보내주세요</div>
                </div>
            </div>
        </div>
    </div>
    <footer></footer>
    <!-- 모달 창 -->
    <div id="reportModal" class="modal">
        <div class="modal-content">
            <h2 class="modal-title">가게 신고</h2>
            <p class="modal-warning">가게 신고 시 돌이킬 수 없으며,<br>허위로 신고를 작성한 경우<br>신고는 무효화 됩니다.</p>
            <label for="reportReason" class="modal-label">신고 사유</label>
            <textarea id="reportReason" class="modal-input"></textarea>
            <button class="modal-submit button">신고하기</button>
        </div>
    </div>
</div>
</body>
</html>
info.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5" xmlns="http://www.w3.org/1999/html"
      xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="/css/style.css" rel="stylesheet">
    <link href="/css/user/main.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/menu/info" method="post" class="info-form">
            <img src="/images/sample.png" alt="음식 이미지" class="menu-image">
            <div class="info">
                <div class="title">짜파게티</div>
                <div class="info">맛있는 짜파게티 김치 미포함</div>
                <div class="info">300원</div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="title-text">매운맛</div>
                    <div class="title-option">필수</div>
                </div>
                <div class="contents">
                    <div class="option">
                        <input type="radio" class="d">
                        <p>맵게 해주세요</p>
                        <p>3000원</p>
                    </div>
                    <div class="option">
                        <input type="radio" class="d">
                        <p>맵게 해주세요</p>
                        <p>3000원</p>
                    </div>
                </div>
            </div>
            <div class="option">
                <div class="title">
                    <div class="title-text">토핑</div>
                    <div class="title-option">선택</div>
                </div>
                <div class="contents">
                    <div class="option">
                        <input type="radio" class="d">
                        <p>계란</p>
                        <p>1000원</p>
                    </div>
                    <div class="option">
                        <input type="radio" class="d">
                        <p>치즈</p>
                        <p>500원</p>
                    </div>
                </div>
            </div>
            <div class="count">
                <div class="count-text">수량</div>
                <div class="count-button">
                    <input type="button" class="minus">-</input>
                    <div class="text">1</div>개
                    <input type="button"  class="plus">+</input>
                </div>
            </div>
            <input type="button" class="submit">300원 담기</input>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

 

order.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="innerBox">
    <header></header>
    <div class="container">
        <form action="/menu/order" method="post">
            <div class="restaurant-name">식당 이름</div>
            <div class="orders">
                <div class="menu">
                    <div class="text">
                        <div class="title">음식이름</div>
                        <div class="menu-price">가격 : 5,000원</div>
                        <div class="option">옵션 : 안맵게 해주세요(-3000원)</div>
                        <div class="total-price">3000원</div>
                    </div>
                </div>
                <div class="num">
                    <button class="minus">-</button>
                    <div class="text"></div>
                    <button class="plus">+</button>
                </div>
            </div>
            <div class="menuplus">
                +메뉴추가
            </div>
            <div class="request">
                <div class="title">요청사항</div>
                <textarea class="contents" id="contents" name="contents" placeholder="내용을 입력하세요" rows="3">1</textarea>
                <button class="submit">3000원 주문하기</button>
            </div>
        </form>
    </div>
    <footer></footer>
</div>
</body>
</html>

main.css

@charset "UTF-8";

/* 메인 이미지 */
.container .restaurant-image{
    width: 100%;
    height: 200px;
    object-fit: cover;
    object-position: center top;
}
/* 타이틀과 신고버튼 */
.container .title{
    width: 100%;
    padding: 0 10px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.container .title .restaurant-name{
    font-weight: bold;
}
.container .title .report-button{
    background-color: transparent;
    color: red;
    border: none;
    font-size: 16px;
}
/* 별표와 리뷰 버튼*/
.container .ratings-reviews{
    padding: 0 10px;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.container .ratings-reviews .ratings{
    display: flex;
}
.container .ratings-reviews .review-button{}

/* 호출 버튼 */
.container .call{
    padding: 0 10px;
    width: 100%;
}
.container .call .call-button {
}

/* 메뉴 공지사항 리뷰 */
.container .buttons{
    border-bottom: 3px solid var(--orange);
    display: flex;
    width: 100%;
}

/* 메뉴 */
.container .buttons .menu-btn{
    border: 0;
    border-radius: 0;
    flex: 1;
}

/* 공지사항 */
.container .buttons .announcement-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
}

/* 리뷰 */
.container .buttons .reviews-btn{
    background-color: inherit;
    color: var(--gray);
    border: 0;
    border-radius: 0;
    flex: 1;
}

/* 버튼을 눌렀을때 보여주는 콘텐츠 영역 */
.container .content{
    width: 100%;
}

/* 메뉴 콘텐츠 */
.container .content .menu-content{
    width: 100%;
}

/* 카테고리 슬라이더 부분 */
.container .content .menu-content .category-content{
    margin: auto;
    width: 90%;
}
.container .content .menu-content .category-content .category{
    margin: 10px;
    border-radius: 20px;
    text-align: center;
    background-color: white;
}
.container .content .menu-content .category-content .category:hover{
     transition: 1s;
     background-color: gray;
 }

/* 메뉴 리스트 */
.container .content .menu-content .menus-container {

}
.container .content .menu-content .menus-container .category-name{}
.container .content .menu-content .menus-container .menu{
    width: 100%;
    display: flex;
    border: 1px solid red;
}
.container .content .menu-content .menus-container .menu .image{
    width: 70px;
    height: 70px;
    overflow: hidden;
}
.container .content .menu-content .menus-container .menu .image img{
    width: 70px;
    height: 70px;
}
.container .content .menu-content .menus-container .menu .text{
    display: flex;
    flex-direction: column;
}
.container .content .menu-content .menus-container .menu .text .menu-name{}
.container .content .menu-content .menus-container .menu .text .menu-info{}
.container .content .menu-content .menus-container .menu .text .menu-price{}
/* 공지사항 콘텐츠 */
.container .content .announcement-content{
    width: 100%;
    display: none;
}

.container .content .announcement-content .announcement {
    width: 100%;
    height: 100px;
    display: flex;
    flex-direction: column;
    padding: 10px;
}
.container .content .announcement-content .announcement .title {
    font-size: var(--font-big);
    color: var(--black);

}
.container .content .announcement-content .announcement .content{
    font-size: var(--font-small);
    color: var(--light-gray);
}
.container .content .announcement-content .announcement .date{
    font-size: var(--font-middle);
    color: var(--gray);
}

/* 리뷰 */
.container .content .review-content{
    width: 100%;
    display: none;
}

.container .content .review-content .review{
    margin: 10px;
    padding: 10px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
}
.container .content .review-content .review .title{
    font-size: var(--font-big);
    color: var(--black);
}
.container .content .review-content .review .mid{
    display: flex;
    font-size: var(--font-small);
    color: var(--light-gray);
}
.container .content .review-content .review .mid .star{}
.container .content .review-content .review .mid .date{}
.container .content .review-content .review .content{
    font-size: var(--font-middle);
    color: var(--gray);
}

/* 모달 창 스타일 */
.modal {
    display: none; /* 초기에는 숨김 */
    position: absolute; /* 화면 중앙에 위치 */
    z-index: 1; /* 내용 위에 표시 */
    left: 0;
    top: 0;
    width: 100%; /* 전체 너비 */
    height: 100%; /* 전체 높이 */
    overflow: auto; /* 내용이 넘칠 경우 스크롤 */
    background-color: rgb(0,0,0); /* 검은색 배경 */
    background-color: rgba(0,0,0,0.4); /* 어두운 투명도 */
}

/* 모달 내용 스타일 */
.modal-content {
    background-color: #fefefe;
    margin: 15% auto; /* 페이지 중앙에 위치 */
    padding: 20px;
    border: 1px solid #888;
    width: 300px; /* 너비 설정 */
    height: 300px; /* 높이 설정 */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.modal-title {
    text-align: center;
    font-weight: bold;
}

.modal-warning {
    color: red;
    text-align: center;
}

.modal-label {
    text-align: left;
}

.modal-input {
    width: 100%;
    height: 100px;
}

.modal-submit {
    background-color: #222; /* 검은색 배경 */
    color: #fff; /* 하얀색 글씨 */
    text-align: center;
}

main.js

const menu = document.querySelector(".menu-btn");
const announcement = document.querySelector(".announcement-btn");
const reviews = document.querySelector(".reviews-btn");
const menuContent = document.querySelector('.menu-content');
const announcementContent = document.querySelector('.announcement-content');
const reviewContent = document.querySelector('.review-content');

menu.addEventListener('click', () => {
    menu.style.backgroundColor = '#ff7a2f';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#fff';
    announcement.style.color = '#717171';
    reviews.style.color = '#717171';

    menuContent.style.display = 'block';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'none';
});

announcement.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = '#ff7a2f';
    reviews.style.backgroundColor = 'inherit';

    menu.style.color = '#717171';
    announcement.style.color = '#fff';
    reviews.style.color = '#717171';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'block';
    reviewContent.style.display = 'none';
});

reviews.addEventListener('click', () => {
    menu.style.backgroundColor = 'inherit';
    announcement.style.backgroundColor = 'inherit';
    reviews.style.backgroundColor = '#ff7a2f';

    menu.style.color = '#717171';
    announcement.style.color = '#717171';
    reviews.style.color = '#fff';

    menuContent.style.display = 'none';
    announcementContent.style.display = 'none';
    reviewContent.style.display = 'block';
});

// 신고 버튼 클릭 이벤트
document.querySelector('.report-button').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'block';
});

// 신고하기 버튼 클릭 이벤트로 모달 창 닫기 (옵션)
document.querySelector('.modal-submit').addEventListener('click', () => {
    document.getElementById('reportModal').style.display = 'none';
});

// 슬릭 슬라이더
$('.category-content').slick({
    dots: false,
    infinite: false,
    speed: 300,
    slidesToShow: 3
});

// 카테고리를 클릭 했을 때 해당 메뉴 부분으로 가능 기능
document.addEventListener('DOMContentLoaded', function() {
    const categories = document.querySelectorAll('.category');

    categories.forEach(category => {
        category.addEventListener('click', function() {
            const categoryId = this.dataset.categoryId;
            scrollToMenuCategory(categoryId);
        });
    });

    function scrollToMenuCategory(categoryId) {
        const menuCategory = document.querySelector(`.menu-category[data-category-id='${categoryId}']`);
        if (menuCategory) {
            menuCategory.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
    }
});

MenuController.java

package kr.ganjuproject.controller;

import kr.ganjuproject.entity.Category;
import kr.ganjuproject.entity.Menu;
import kr.ganjuproject.service.CategoryService;
import kr.ganjuproject.service.MenuService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;
import java.util.Optional;

@Controller
@Slf4j
@RequestMapping("/menu")
@RequiredArgsConstructor
public class MenuController {

    private final MenuService menuService;
    private final CategoryService categoryService;

    @GetMapping("/main")
    public String main(Model model) {
        List<Category> categories = categoryService.getList();
        model.addAttribute("categories", categories);
        List<Menu> menus = menuService.getList();
        model.addAttribute("menus", menus);
        return "menu/main";
    }

    @GetMapping("/info/{id}")
    public String info(@PathVariable Long id, Model model){
        Optional<Menu> menu = menuService.findById(id);

        if(menu.isPresent()) {
            Menu m = menu.get();
            model.addAttribute("menu", m);
            return"menu/info";
        }else{
            return "redirect:/menu/main";
        }
    }
    @GetMapping("/cart")
    public String cart(){
        return"menu/cart";
    }
    @GetMapping("/order")
    public String order(){
        return"menu/order";
    }
    @GetMapping("/review")
    public String review(){
        return"menu/review";
    }
}

MenuService.java

package kr.ganjuproject.service;

import kr.ganjuproject.entity.Menu;
import kr.ganjuproject.repository.MenuRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MenuService {

    private final MenuRepository menuRepository;

    public List<Menu> getList() {
        return menuRepository.findAll();
    }

    public Optional<Menu> findById(Long id){return menuRepository.findById(id);}
}

MenuRepository.java

package kr.ganjuproject.repository;

import kr.ganjuproject.entity.Menu;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MenuRepository extends JpaRepository<Menu, Long> {
}

CategoryService.java

package kr.ganjuproject.service;

import kr.ganjuproject.entity.Category;
import kr.ganjuproject.repository.CategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CategoryService {
    private final CategoryRepository categoryRepository;

    public List<Category> getList(){
        return categoryRepository.findAll();
    }
}

어제는 결재 테스트 한다고 아무것도 못한 느낌이 었는데 오늘은 할만했

'공부 > Ganju' 카테고리의 다른 글

[Spring/AWS] 팀프로젝트 7일차  (0) 2024.04.04
[Spring/AWS] 팀프로젝트 6일차  (0) 2024.04.03
[Spring/AWS] 팀프로젝트 4일차  (0) 2024.04.02
[Spring/AWS] 팀프로젝트 3일차  (0) 2024.03.29
[Spring/AWS] 팀프로젝트 2일차  (0) 2024.03.28

+ Recent posts