관리 메뉴

거니의 velog

(3) 랜딩 페이지 > 코드 리뷰 본문

대덕인재개발원_최종 포트폴리오

(3) 랜딩 페이지 > 코드 리뷰

Unlimited00 2024. 2. 15. 11:35

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<c:set value="${pageContext.request.contextPath }" var="contextPath" />
<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8" />
        <meta name="author" content="부루마불" />
        <meta name="description" content="대덕인재개발원 7월반 4조 부루마불의 여기갈래 프로젝트입니다." />
        <meta name="keywords" content="부루마불, 여기갈래, 여행, 통합, 플랫폼" />
        <meta name="copyright" content="대덕인재개발원" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>여기갈래 &gt; 랜딩 페이지</title>
        
        <!-- 사용자 페이지 > 헤더세팅 영역 -->
        <tiles:insertAttribute name="headerSettings" />
    	
        <%-- <c:remove var="message" scope="page" />
        <c:remove var="message" scope="session" />
        <c:remove var="message" scope="request" />
        <c:remove var="message" scope="application" /> --%>
        
    	<c:if test="${not empty message }">
		    <script type="text/javascript">
		        //alert("${message}");
		        $(function(){
		        	// 성공시
		        	<c:if test="${msgflag eq 'su'}">
		        		Swal.fire({
		                    title: "성공",
		                    text: "${message}",
		                    icon: "success"
		                });
		        	</c:if>
		        	// 실패시
		        	<c:if test="${msgflag eq 'fa'}">
			        	Swal.fire({
		                    title: "실패",
		                    text: "${message}",
		                    icon: "error"
		                });
		        	</c:if>
		        	// 정보성 메시지
		        	<c:if test="${msgflag eq 'in'}">
			        	Swal.fire({
		                    title: "안내",
		                    text: "${message}",
		                    icon: "info"
		                });
		        	</c:if>
		        });
		        <c:remove var="message" scope="request" />
			    <c:remove var="message" scope="session" />
		    </script>
		</c:if>
		
		<!-- 
    		내부 CSS 
    		- 내부 CSS는 외부 CSS의 스타일링을 건드리지 않기 위해서
    		개별적인 페이지에다 스타일을 적용할 때 종종 쓴다.
    		- 보통 프로토타이핑 테스트용이나, FE Leader의 판단에 따라 여기에
    		추가적인 스타일을 작성한다.
    	-->
    	<style>
    		.msub a {
    			color: #333;
    		}
    	</style>
		
	</head>
    <body class="scroll">
        
        <!-- 사용자 페이지 > 헤더 영역 -->
        <tiles:insertAttribute name="header" />
        
        <!-- 사용자 페이지 > 랜딩 페이지 구현 영역 -->
        <tiles:insertAttribute name="userMainContainer" />
        
        <!-- 사용자 페이지 > 푸터 영역 -->
		<tiles:insertAttribute name="footer" />
		
		<!-- 사용자 페이지 > 자바스크립트 세팅 영역 -->
		<tiles:insertAttribute name="settings" />

    </body>
</html>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!-- 랜딩페이지 css -->
<link href="${contextPath }/resources/css/index.css" rel="stylesheet" />

<!-- 메인 슬라이드 구역 -->
<section class="mainSlideContainer">
	<div class="mainSlideTxt">
		<h2>
			<span>YoGi gAlE</span>
			<br />
			TRIP ROAD
		</h2>
		<p>
			우리 지금, 떠나요!<br />
			가장 쉽게! 가장 빠르게! 가장 즐겁게!<br />
			대한민국의 중심으로 통하는 길
 		</p>
	</div>
    <article class="mainSlider">
        <div>
            <img src="${contextPath }/resources/images/slide1.jpg" alt="슬라이드 이미지" />
        </div>
        <div>
            <img src="${contextPath }/resources/images/slide2.jpg" alt="슬라이드 이미지" />
        </div>
        <div>
            <img src="${contextPath }/resources/images/slide3.jpg" alt="슬라이드 이미지" />
        </div>
        <div>
            <img src="${contextPath }/resources/images/slide4.jpg" alt="슬라이드 이미지" />
        </div>
        <div>
            <img src="${contextPath }/resources/images/slide5.jpg" alt="슬라이드 이미지" />
        </div>
    </article>
</section>

<!-- 여행 정보 구역 -->
<section class="journeyInfoContainer cen">
    <h3>어디로 여행을 떠나시나요?</h3>
    <div class="journeyInfoSearchContents">
        <div>
            <input type="text" class="form-control" id="jourInfoSearch" name="jourInfoSearch" placeholder="도시명으로 검색해보세요." />
            <button type="button" id="searchBtn">
                <i class="fas fa-search"></i>
            </button>
        </div>
    </div>
    <div class="journeyInfoContents">
    	<!-- 반복 시작 구간 -->
    	<c:choose>
    		<c:when test="${not empty journeyList8 }">
    			<c:forEach items="${journeyList8 }" var="journey">
    				<article>
			            <div class="infoThumbnailBox">
			                <img src="${journey.infoPreviewimg }" alt="여행 정보 썸네일 이미지" />
			            </div>
			            <div>
			                <h4 class="textDrop">${journey.infoEngname }</h4>
			                <span class="infoTitle textDrop">${journey.infoName }</span>
			                <p>
								${journey.infoDescription }
			                </p>
			                <c:if test="${journey.infoFlightyn eq 'y' }">
				                <span class="airportText">
				                    <span>
				                    	<c:if test="${journey.infoFlight eq 'str' }">
				                    		직항
				                    	</c:if>
				                    	<c:if test="${journey.infoFlight eq 'round' }">
				                    		왕복
				                    	</c:if>
				                    </span>
				                    <span>${journey.infoFlighttime }</span>
				                </span>
			                </c:if>
			                <c:if test="${journey.infoVisayn eq 'y' }">
			                	<span class="visaText">
				                    <span>
				                    	<c:if test="${journey.infoVisaexp eq 'visa' }">
				                    		비자
				                    	</c:if>
				                    	<c:if test="${journey.infoVisaexp eq 'none' }">
				                    		무비자
				                    	</c:if>
				                    </span>
				                    <span>${journey.infoVisatime }</span>
				                </span>
			                </c:if>
			                <span class="voltageTxt">
			                	<span>콘센트</span>
			                	<span>${journey.infoVoltage }</span>
			                </span>
			                <span class="infoTimediferTxt">
			                	<span>한국대비</span>
			                	<span>${journey.infoTimedifer }</span>
			                </span>
			                <input type="hidden" class="infoNo" name="infoNo" value="${journey.infoNo }" />
			            </div>
			        </article>
    			</c:forEach>
    		</c:when>
    		<c:otherwise>
    			<article style="text-align: center; width: 50%; margin: 0px auto; float: none; cursor: auto; background-color: #333; color: white; padding: 20px; border-radius: 4px;">
    				등록된 여행 정보가 없습니다.
    			</article>
    		</c:otherwise>
    	</c:choose>
    </div>
</section>

<!-- 여행 정보 모달창 -->
<section class="infoModalContents">
    <div class="infoModalBox cen">
        <div class="infoModalClose">
            <div></div>
            <div></div>
        </div>
        <article class="infoModalLeft">
            <div>
                <div class="modalInfoSetting">
                    <span>여행 장소(영어)</span>
                    <h5>여행 장소</h5>
                    <p>여행 내용</p>
                </div>
                <div class="infoModalFourSection">
                    <div>
                        <div>
                            <i class="fas fa-plane"></i>
							항공
                        </div>
                        <div>
                            <span>없음</span>
                            <span>-</span>
                        </div>
                    </div>
                    <div>
                        <div>
                            <i class="fas fa-passport"></i>
							비자
                        </div>
                        <div>
                            <span>없음</span>
                            <span>-</span>
                        </div>
                    </div>
                    <div>
                        <div>
                            <i class="fas fa-plug"></i>
							전압
                        </div>
                        <div>
                            <span>콘센트</span>
                            <span>220V</span>
                        </div>
                    </div>
                    <div>
                        <div>
                            <i class="fas fa-clock"></i>
							시차
                        </div>
                        <div>
                            <span>한국대비</span>
                            <span>없음</span>
                        </div>
                    </div>
                </div>
                <a href="/myplan/makeplan.do" class="btn btn-info makePlanSty <c:if test="${empty sessionInfo }">noUserBlockModal</c:if>" style="color: white;">
					일정만들기 
                    <i class="fas fa-chevron-right"></i>
                </a>
            </div>
        </article>
        <article class="infoModalRight">
            <div class="infoModalImgBox">
                <img src="${contextPath }/resources/images/Jeju.jpg" alt="여행 정보 이미지" />
            </div>
        </article>
    </div>
</section>

<!-- 플래너 이동 표출 영역 -->
<section class="plannerMoveContainer">
    <div class="plannerContents">
        <h4>여행을 떠나요</h4>
        <p>
			대한민국은 다양한 자연 경관과 역사적 명소로 가득한 아름다운 나라입니다. 
            <br />
			전통 음식과 문화체험도 풍부하며, 한국의 아름다움과 다양성을 국내 여행으로 만나보세요.
        </p>
        <div>
            <button type="button" class="btn btn-secondary plannerMoveBtn">플래너로 이동</button>
        </div>
    </div>
</section>

<!-- 사이트 그래프 표출 영역 -->
<section class="siteGraphContainer cen">
    <h3>누가 어디로 여행을 떠날까요?</h3>
    <div class="siteGraphContents">
        <article class="siteGraph">
            <h4>성별 사이트 이용자 수(%)</h4>
            <canvas id="genderPieGraph"></canvas>
        </article>
        <article class="siteGraph">
            <h4>주간 연령별 사이트 이용자 수</h4>
            <canvas id="ageBarGraph"></canvas>
        </article>
        <article class="siteGraph">
            <h4>여행지역 선호도(%)</h4>
            <canvas id="areaPolarGraph"></canvas>
        </article>
        <article class="siteGraph">
            <h4>주간 방문자 현황(명)</h4>
            <canvas id="visitorStackGraph"></canvas>
        </article>
    </div>
</section>

<!-- 랜딩페이지 js -->
<script src="${contextPath }/resources/js/index.js"></script>
<!-- slick slide -->
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css"/>
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>
<!-- chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<script>
	$(function(){
		$.startHeaderStyle();
        $.mainSlideSlickFn();
        $(".mainSlider").mainSlideArrowStyle();
        $(".slick-dots").mainSlideDotsStyle();
        $.JourneyInfoModalFn();
        $.startHeaderEmpty();
        $.ajaxIndexJourneyInfoSearchFn();
        $.makeplanClickEvent();
        $(".plannerMoveBtn").click(function(){
        	location.href = "/myplan/planMain.do";
        });
        
        /* 종횡비 함수 */
        $.eachJourneyInfoImgResizeFn();
        var infoModalImgBox = $(".infoModalImgBox");
        var infoModalImg = $(".infoModalImgBox img");
        $.ratioBoxH(infoModalImgBox, infoModalImg);
        
        /* chart.js */
        
        /*
			성별 사이트 이용자 수(%)
            var genderPercent 에 담을 파라미터 값
            param1 : 남자 성비(%)
            param2 : 여자 성비(%)
        */
        function getRandomInt(min, max) {
       	  return Math.floor(Math.random() * (max - min + 1)) + min;
       	}

       	function generateRandomGenderPercent() {
       	  var malePercent = getRandomInt(10, 90); // 최소값을 10으로 설정
       	  var femalePercent = 100 - malePercent;

       	  return [malePercent, femalePercent];
       	}

       	var genderPercent = generateRandomGenderPercent();

       	console.log("성별 사이트 이용자 수(%):", genderPercent);
       	
        $.genderPieGraph(genderPercent);
        
        /*
		        연령별 사이트 이용 현황(%)
	        var ageWeekUseSiteCnt 에 담을 파라미터 값
	        param1 : 주간 10대 이용 현황(명)
	        param2 : 주간 20대 이용 현황(명)
	        param3 : 주간 30대 이용 현황(명)
	        param4 : 주간 40대 이용 현황(명)
	        param5 : 주간 50대 이용 현황(명)
	        param6 : 주간 60대 이용 현황(명)
	    */
	    function getRandomInt(min, max) {
    	  return Math.floor(Math.random() * (max - min + 1)) + min;
    	}

    	function generateRandomAgeWeekUseSiteCnt() {
    	  var ageWeekUseSiteCnt = [];

    	  // 주간 이용 현황을 랜덤으로 생성
    	  for (var i = 1; i <= 6; i++) {
    	    var randomValue = getRandomInt(100, 300); // 최소값을 100으로 설정
    	    ageWeekUseSiteCnt.push(randomValue);
    	  }

    	  return ageWeekUseSiteCnt;
    	}

    	var ageWeekUseSiteCnt = generateRandomAgeWeekUseSiteCnt();

    	console.log("연령별 사이트 이용 현황(%):", ageWeekUseSiteCnt);
	        
	    $.ageMixedGraph(ageWeekUseSiteCnt);
        
        /*
			여행지역 선호도(%)
            var nineSectionPreference 에 담을 파라미터 값
            param1 : 경기도 선호도(%)
            param2 : 강원도 선호도(%)
            param3 : 충청남도 선호도(%)
            param4 : 충청북도 선호도(%)
            param5 : 전라남도 선호도(%)
            param6 : 전라북도 선호도(%)
            param7 : 경상남도 선호도(%)
            param8 : 경상북도 선호도(%)
            param9 : 제주특별자치도 선호도(%)
        */
        function getRandomInt(min, max) {
       	  return Math.floor(Math.random() * (max - min + 1)) + min;
       	}

       	function generateRandomPreferences() {
       	  var preferences = [];

       	  // 8개의 선호도를 랜덤으로 생성
       	  for (var i = 1; i <= 8; i++) {
       	    var randomValue = getRandomInt(10, 100); // 최소값을 10으로 설정
       	    preferences.push(randomValue);
       	  }

       	  // 9번째 선호도를 설정
       	  var sumOfPreferences = preferences.reduce((acc, val) => acc + val, 0);
       	  preferences.push(Math.max(10, 100 - sumOfPreferences)); // 최소값을 10으로 설정

       	  // 배열을 랜덤하게 섞기
       	  preferences.sort(() => Math.random() - 0.5);

       	  return preferences;
       	}

       	var nineSectionPreference = generateRandomPreferences();

       	console.log("여행지역 선호도(%):", nineSectionPreference);
       	
        $.areaPolarGraph(nineSectionPreference);
        
        /*
			주간 방문자 현황(명)
	        var weekVisiterAgeCnt 에 담을 파라미터 값
	        teen : 10대, 오늘 기준으로 일주일치 방문자 수
	        twenties : 20대, 오늘 기준으로 일주일치 방문자 수
	        thirties : 30대, 오늘 기준으로 일주일치 방문자 수
	        forties : 40대, 오늘 기준으로 일주일치 방문자 수
	        fifties : 50대, 오늘 기준으로 일주일치 방문자 수
	        sixties : 60대, 오늘 기준으로 일주일치 방문자 수
	    */
	    function getRandomInt(min, max) {
    	  return Math.floor(Math.random() * (max - min + 1)) + min;
    	}

    	function randomizeWeekVisiterAgeCnt() {
    	  var randomizedWeekVisiterAgeCnt = {};

    	  // 각 연령별 배열을 랜덤하게 생성
    	  randomizedWeekVisiterAgeCnt.teen = Array.from({ length: 7 }, () => getRandomInt(10, 30));
    	  randomizedWeekVisiterAgeCnt.twenties = Array.from({ length: 7 }, () => getRandomInt(10, 30));
    	  randomizedWeekVisiterAgeCnt.thirties = Array.from({ length: 7 }, () => getRandomInt(10, 30));
    	  randomizedWeekVisiterAgeCnt.forties = Array.from({ length: 7 }, () => getRandomInt(10, 30));
    	  randomizedWeekVisiterAgeCnt.fifties = Array.from({ length: 7 }, () => getRandomInt(10, 30));
    	  randomizedWeekVisiterAgeCnt.sixties = Array.from({ length: 7 }, () => getRandomInt(10, 30));

    	  return randomizedWeekVisiterAgeCnt;
    	}

    	var randomizedWeekVisiterAgeCnt = randomizeWeekVisiterAgeCnt();

    	console.log("랜덤으로 배치된 주간 방문자 현황(명):", randomizedWeekVisiterAgeCnt);
	    
	    $.visitorStackGraph(randomizedWeekVisiterAgeCnt);
        
	});
</script>

@import url('https://fonts.cdnfonts.com/css/spongition');

/* 랜딩 페이지 스타일링 */
.mainSlideContainer {
    position: relative;
}

.mainSlideTxt {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 40;
    color: white;
    left: 200px;
}

.mainSlideTxt h2 {
    font-family: 'Spongition', sans-serif;
    font-weight: 800;
    font-size: 5rem;
}

.mainSlideTxt p {
    font-size: 1.9rem;
}

.mainSlideTxt h2 span {
    color: yellow;
}

/* 슬라이드 스타일 */
.mainSlideContainer .mainSlider {
    position: relative;
}

.mainSlider img {
    width: 100%;
}

.mainSlider .slick-dots {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: 15px;
    list-style: none;
    margin-bottom: 0px;
    padding-left: 0px;
    overflow: auto;
}

.mainSlider .slick-dots li {
    float: left;
}

.mainSlider .slick-dots button {
    display: block;
    border-radius: 50%;
    background-color: transparent;
    border: none;
    color: white;
    font-size: 1.1rem;
    text-align: center;
    margin: 0px 5px;
}

.mainSlider .slick-active button {
    color: black;
}

.mainSlider .slick-active i {
    opacity: 0.6;
}

.mainSlider .slick-prev,
.mainSlider .slick-next {
    position: absolute;
    top: 55%;
    transform: translateY(-50%);
    z-index: 50;
    background-color: rgba(0,0,0,0.6);
    border: none;
    outline: none;
    padding: 20px 10px;
    text-align: center;
    border-radius: 4px;
    color: white;
    font-size: 2rem;
    transition: all 0.4s;
}

.mainSlider .slick-prev:hover,
.mainSlider .slick-next:hover {
    background-color: white;
    color: black;
}

.mainSlider .slick-prev {
    left: 15px;
}

.mainSlider .slick-next {
    right: 15px;
}

/* 여행 정보 스타일 */
.journeyInfoContainer {
    position: relative;
    padding: 50px 0px;
}

.journeyInfoContainer h3 {
    text-align: center;
    font-size: 1.5rem;
    font-weight: 700;
    padding: 20px 0px;
    margin-bottom: 0px;
}

.journeyInfoSearchContents>div {
    overflow: auto;
    position: relative;
    width: 600px;
    margin: 0px auto;
}

.journeyInfoSearchContents {
    padding: 30px 0px;
}

.journeyInfoSearchContents input,
.journeyInfoSearchContents button {
    float: left;
}

.journeyInfoSearchContents input {
    width: 100%;
}

.journeyInfoSearchContents input::placeholder {
    color: #dee2e6;
}

.journeyInfoSearchContents button {
    display: block;
    width: 20px;
    height: 100%;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    right: 10px;
    border: none;
    background-color: transparent;
    color: #dee2e6;
    transition: all 0.4s;
}

.journeyInfoSearchContents button:hover {
    color: black;
}

.journeyInfoContents {
    height: 840px;
    margin: 25px 0px;
}

.journeyInfoContents article {
    float: left;
    width: calc(100% / 4 - 30px);
    margin-right: 40px;
    cursor: pointer;
    transition: all 0.4s;
}

.journeyInfoContents article:hover {
    transform: scale(1.03);
}

.journeyInfoContents article:nth-of-type(4),
.journeyInfoContents article:last-of-type {
    margin-right: 0px;
}

.journeyInfoContents .infoThumbnailBox {
    position: relative;
    overflow: hidden;
    height: 320px;
    border-radius: 10px;
}

.journeyInfoContents .infoThumbnailBox img {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.journeyInfoContents h4 {
    margin-bottom: 5px;
}

.journeyInfoContents article>div:last-of-type {
    padding: 20px 0px;
}

.journeyInfoContents article>div:last-of-type span {
    display: block;
    font-weight: 300;
}

.journeyInfoContents article p,
.journeyInfoContents article .airportText,
.journeyInfoContents article .visaText,
.journeyInfoContents article .voltageTxt,
.journeyInfoContents article .infoTimediferTxt {
    display: none!important;
}

/* 여행 정보 모달창 */
.infoModalContents {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba(0,0,0,0.3);
    z-index: 70;
    display: none;
}

.infoModalBox {
    position: absolute;
    top: 55%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: white;
    padding: 60px;
    overflow: auto;
    border-radius: 10px;
}

.infoModalClose {
    width: 30px;
    height: 30px;
    position: absolute;
    top: 20px;
    right: 20px;
    cursor: pointer;
    background-color: rgba(0,0,0,0.4);
    border-radius: 4px;
}

.infoModalClose div {
    width: 25px;
    height: 1px;
    background-color: white;
    position: absolute;
    top: 50%;
    left: 50%;
}

.infoModalClose div:first-of-type {
    transform: translate(-50%, -50%) rotate(45deg);
}

.infoModalClose div:last-of-type {
    transform: translate(-50%, -50%) rotate(-45deg);
}

.infoModalBox article {
    float: left;
    width: calc(100% / 2);
    padding: 24px;
}

.infoModalLeft>div {
    height: 400px;
    position: relative;
}

.modalInfoSetting span {
    color: #AAB1B8;
    font-size: 20px;
    font-weight: 300;
}

.modalInfoSetting h5 {
    font-size: 36px;
    font-weight: 700;
    margin-bottom: 20px;
}

.modalInfoSetting p {
    font-weight: 300;
    font-size: 14px;
    margin-bottom: 20px;
}

.infoModalImgBox {
    position: relative;
    overflow: hidden;
    height: 400px;
}

.infoModalImgBox img {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.infoModalFourSection {
    overflow: auto;
}

.infoModalFourSection>div {
    float: left;
    width: calc(100% / 4);
}

.infoModalFourSection>div>div:first-of-type {
    margin-bottom: 10px;
}

.infoModalFourSection>div>div:last-of-type span {
    display: block;
    margin-bottom: 10px;
}

.infoModalFourSection>div>div:last-of-type span:first-of-type {
    color: #AAB1B8;
    font-size: 12px;
}

.infoModalFourSection>div>div:last-of-type span:last-of-type {
    color: #737E8A;
    font-size: 14px;
}

.makePlanSty {
    display: block;
    position: absolute;
    bottom: 0;
    padding: 8px 30px;
}

.makePlanSty i {
    margin-left: 10px;
}

.noUserBlockModal::before {
    content: "";
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: rgba(0,0,0,0.6);
}

.noUserBlockModal::after {
    content: "회원전용";
    display: block;
    width: 80px;
    height: 20px;
    background-color: #0dcaf0;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border-radius: 4px;
    font-size: 0.8rem;
}

/* 그래프 디자인 영역 */
.siteGraphContainer {
    position: relative;
    padding-bottom: 100px;
}

.siteGraphContainer h3 {
    text-align: center;
    font-size: 1.5rem;
    font-weight: 700;
    padding: 20px 0px;
    margin-bottom: 0px;
}

.siteGraphContents {
    height: 800px;
}

.siteGraph {
    float: left;
    width: calc(100% / 2);
    height: 400px;
    padding: 40px 30px;
}

.siteGraph h4 {
    font-size: 1.2rem;
}

/* 플래너 이동 표출 영역 */
.plannerMoveContainer {
    height: 500px;
    background-image: url(../images/plannerMoveBgImg.jpg);
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
    background-attachment: fixed;
    margin-bottom: 40px;
    position: relative;
}

.plannerContents {
    width: 400px;
    height: 250px;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background-color: rgba(255, 255, 255, 0.6);
    border-radius: 4px;
    padding: 30px;
}

.plannerContents h4 {
    font-size: 1.8rem;
    font-weight: 500;
    letter-spacing: 0px;
    margin-bottom: 10px;
}

.plannerContents p {
    font-size: 1.05rem;
    font-weight: 300;
    letter-spacing: 0px;
    margin-bottom: 10px;
}

.plannerContents>div {
    text-align: right;
}

/* 미디어 쿼리 (PC용) */
@media all and (max-width: 1600px) {
    
    .mainSlideTxt h2 {
        font-size: 4rem;
    }

    .mainSlideTxt p {
        font-size: 1.5rem;
    }
    
}

@media all and (max-width: 1400px) {
    
    .mainSlideTxt {
        top: 60%;
        left: 150px;
    }
    
    .mainSlideTxt h2 {
        font-size: 3rem;
    }

    .mainSlideTxt p {
        font-size: 1.2rem;
    }
    
}

@media all and (max-width: 1300px) {
    
    .mainSlideTxt h2 {
        font-size: 2.5rem;
    }

    .mainSlideTxt p {
        font-size: 1rem;
    }
    
    .journeyInfoContainer {
        padding: 50px 20px;
    }
    
}

@media all and (max-width: 1060px) {
    
    .journeyInfoContents article {
        width: calc(100% / 2 - 20px);
    }
    
    .journeyInfoContents {
        height: 1680px;
        margin: 0px;
    }
    
    .journeyInfoContents article:nth-of-type(2),
    .journeyInfoContents article:nth-of-type(4),
    .journeyInfoContents article:nth-of-type(6),
    .journeyInfoContents article:last-of-type {
        margin-right: 0px;
    }
    
    /* 모달창 스타일 */
    .infoModalBox {
        padding: 30px;
    }
    
    .infoModalBox article {
        float: none;
        width: 100%;
        padding-top: 100px;
    }
    
    .infoModalBox article:first-of-type {
        padding-top: 0px;
    }
    
    .infoModalLeft>div,
    .infoModalImgBox {
        height: 200px;
    }
    
    .makePlanSty {
        bottom: 120px;
        right: 0;
        border: none;
    }
     
}

@media all and (max-width: 900px) {
    
    .mainSlideTxt {
        left: 100px;
    }
    
    .mainSlideTxt h2 {
        font-size: 2rem;
    }

    .mainSlideTxt p {
        font-size: 0.8rem;
    }
    
    .mainSlider .slick-prev,
    .mainSlider .slick-next {
        top: 60%;
        padding: 15px 7px;
        font-size: 1.4rem;
    }
    
    .mainSlider .slick-dots button {
        font-size: 0.9rem;
    }
    
    .siteGraphContents {
        height: 1600px;
    }

    .siteGraph {
        float: none;
        width: 100%;
    }
    
}

/* 미디어 쿼리 (태블릿용) */
@media all and (max-width: 760px) {
    
    .mainSlideTxt h2,
    .mainSlideTxt p {
        display: none;
    }
    
}

@media all and (max-width: 680px) {
    
    .mainSlider .slick-prev,
    .mainSlider .slick-next {
        display: none;
    }
    
    .mainSlider .slick-dots {
        display: none;
    }
    
    .journeyInfoContents {
        margin: 0px;
    }
    
    .journeyInfoContents article {
        width: 100%;
        margin-right: 0px;
    }
    
    .journeyInfoContents article:nth-of-type(5),
    .journeyInfoContents article:nth-of-type(6),
    .journeyInfoContents article:nth-of-type(7),
    .journeyInfoContents article:last-of-type {
        display: none;
    }
    
    .journeyInfoSearchContents {
        padding: 30px 0px;
    }
    
    .journeyInfoSearchContents>div {
        width: auto;
    }
    
    .infoModalBox article {
        padding-top: 150px;
    }
    
    .infoModalBox article:first-of-type {
        padding-top: 0px;
    }
    
    .plannerContents {
        width: 300px;
        height: auto;
    }
    
}

@media all and (max-width: 500px) {
    
    .makePlanSty {
        display: none;
    }
    
    .plannerContents {
        width: 250px;
        height: auto;
    }
    
}

// 공통 함수
// 메인 슬라이드 > slick
// 일시 정지 속성 : slickPause
// 재생 속성 : slickPlay
$.mainSlideSlickFn = function () {
    $(".mainSlider").slick({
        dots: true,
        infinite: true,
        fade: true,
        speed: 2000,
        slidesToShow: 1,
        autoplay: true,
        autoplaySpeed: 2000
    });
};

$.fn.mainSlideArrowStyle = function () {
    var slickPrev = this.find(".slick-prev");
    var slickNext = this.find(".slick-next");
    slickPrev.html("<i class='fas fa-chevron-left'></i>");
    slickNext.html("<i class='fas fa-chevron-right'></i>");
};

$.fn.mainSlideDotsStyle = function () {
    var slickDots = this.find("[role=tab]");
    slickDots.html("<i class='fas fa-circle'></i>");
};

// 여행 정보 모달창 기능
$.JourneyInfoModalFn = function () {
    // 모달창 닫기
    var infoModalClose = $(".infoModalClose");
    var infoModalContents = $(".infoModalContents");
    infoModalClose.click(function () {
        infoModalContents.hide();
    });
    // 모달창 열기
    var journeyInfoContents = $(".journeyInfoContents article");
    var modalInfoSetting = $(".modalInfoSetting");
    journeyInfoContents.click(function () {
        
        var thisIs = $(this);
        var infoImgSrc = thisIs.find("img").attr("src").trim();
        var infoH4 = thisIs.find("h4").text().trim();
        var infoSpan = thisIs.find(".infoTitle").text().trim();
        var infoP = thisIs.find("p").text().trim();
        
        modalInfoSetting.find("span").text(infoH4);
        modalInfoSetting.find("h5").text(infoSpan);
        modalInfoSetting.find("p").text(infoP);
        var infoModalImgBox = $(".infoModalImgBox");
        infoModalImgBox.find("img").attr("src", infoImgSrc);
        
        var airportText = thisIs.find(".airportText").html();
        var airportHtmlEl = $(".infoModalFourSection>div:first-of-type>div:last-of-type");
        airportHtmlEl.html("");

        var visaText = thisIs.find(".visaText").html();
        var visaHtmlEl = $(".infoModalFourSection>div:nth-of-type(2)>div:last-of-type");
        visaHtmlEl.html("");

        var voltageTxt = thisIs.find(".voltageTxt").html();
        var voltageHtmlEl = $(".infoModalFourSection>div:nth-of-type(3)>div:last-of-type");
        voltageHtmlEl.html("");

        var infoTimediferTxt = thisIs.find(".infoTimediferTxt").html();
        var infoTimediferHtmlEl = $(".infoModalFourSection>div:last-of-type>div:last-of-type");
        infoTimediferHtmlEl.html("");

        var emptyHtmlEl = "<span>없음</span><span>-</span>";
        
        if(airportText != undefined) {
            airportHtmlEl.html(airportText);
        }else {
            airportHtmlEl.html(emptyHtmlEl);
        }

        if(visaText != undefined) {
            visaHtmlEl.html(visaText);
        }else {
            visaHtmlEl.html(emptyHtmlEl);
        }

        if(voltageTxt != undefined) {
            voltageHtmlEl.html(voltageTxt);
        }else {
            voltageHtmlEl.html(emptyHtmlEl);
        }

        if(infoTimediferTxt != undefined) {
            infoTimediferHtmlEl.html(infoTimediferTxt);
        }else {
            infoTimediferHtmlEl.html(emptyHtmlEl);
        }
        
        var infoNoVal = thisIs.find(".infoNo").val();
        $("#choiceInfoNo").val(infoNoVal);
        
        infoModalContents.show();
        
        var infoModalImgBox = $(".infoModalImgBox");
        var infoModalImg = $(".infoModalImgBox img");
        $.ratioBoxH(infoModalImgBox, infoModalImg);
        
    });
};

// chart.js
$.genderPieGraph = function(genderPercent){
    const ctx1 = document.querySelector("#genderPieGraph");
    var pieChart = new Chart(ctx1, {
        type: 'pie',
        data: {
            labels: ['남자', '여자'],
            datasets: [
                {
                    label: '성별 사이트 이용자 수(%)',
                    data: genderPercent,
                    borderWidth: 1
                }
            ]
        },
        options: {
            responsive: true, // Enable responsiveness
            maintainAspectRatio: false, // Allow aspect ratio to be adjusted
            scales: {
                y: {
                    beginAtzero: true
                }
            }
        }
    });
};

// 파스텔톤 랜덤 색상 함수
function generateRandomPastelColor() {
    const hue = Math.floor(Math.random() * 360);
    const pastelColor = `hsl(${hue}, 70%, 80%)`;
    return pastelColor;
}

// 주어진 주간 연령별 사이트 이용 고객 수 배열을 기반으로
// 각 연령대의 사이트 이용 비율을 계산하여 퍼센트 배열을 반환하는 함수
function calculatePercentage(ageWeekUseSiteCnt) {
    // 주어진 배열의 총 합을 계산
    const totalCustomers = ageWeekUseSiteCnt.reduce((total, count) => total + count, 0);

    // 각 연령대의 사이트 이용 비율을 계산하여 퍼센트로 변환
    const percentageValues = ageWeekUseSiteCnt.map(count => ((count / totalCustomers) * 100).toFixed(2));

    return percentageValues;
}

$.ageMixedGraph = function(ageWeekUseSiteCnt){
    var ageUseSitePercent = calculatePercentage(ageWeekUseSiteCnt);
    console.log(ageUseSitePercent);
    
    const ctx2 = document.querySelector("#ageBarGraph");
    var mixedChart = new Chart(ctx2, {
        data: {
            labels: ['10대', '20대', '30대', '40대', '50대', '60대 이상'],
            datasets: [
                {
                    type: 'line',
                    label: '주간 연령별 사이트 이용 현황(%)',
                    data: ageUseSitePercent,
                    borderWidth: 1,
                    backgroundColor: 'rgba(255, 99, 132, 1)',
                    borderColor: 'rgba(255, 99, 132, 1)',
                    yAxisID: 'percentageYAxis'
                },
                {
                    type: 'bar',
                    label: '주간 연령별 사이트 이용 고객 수(명)',
                    data: ageWeekUseSiteCnt,
                    borderWidth: 1,
                    backgroundColor: Array.from({ length: 6 }, () => generateRandomPastelColor()),
                    yAxisID: 'countYAxis'
                }
            ]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            scales: {
                percentageYAxis: {
                    type: 'linear',
                    position: 'left',
                    title: {
                        display: true,
                        text: '사이트 이용 비율 (%)'
                    },
                    ticks: {
                        beginAtZero: true,
                        max: 100
                    }
                },
                countYAxis: {
                    type: 'linear',
                    position: 'right',
                    title: {
                        display: true,
                        text: '사이트 이용 고객 수(명)'
                    },
                    ticks: {
                        beginAtZero: true
                    }
                }
            }
        }
    });
};

$.areaPolarGraph = function(nineSectionPreference){
    const ctx3 = document.querySelector("#areaPolarGraph");
    var polarChart = new Chart(ctx3, {
        type: 'polarArea',
        data: {
            // 9개 광역행정구역(도)
            labels: ['경기도', '강원도', '충청남도', '충청북도', '전라남도', '전라북도', '경상남도', '경상북도', '제주특별자치도'],
            datasets: [
                {
                    label: '여행지역 선호도(%)',
                    data: nineSectionPreference,
                    borderWidth: 1
                }
            ]
        },
        options: {
            responsive: true, // Enable responsiveness
            maintainAspectRatio: false, // Allow aspect ratio to be adjusted
            scales: {
                y: {
                    beginAtzero: true
                },
                r: {
                    pointLabels: {
                        display: true,
                        centerPointLabels: true,
                        font: {
                            size: 14
                        }
                    }
                }
            },
        }
    });
};

// 객체 비구조화 할당 : weekVisiterAgeCnt = {teen, twenties, thirties, forties, fifties, sixties};
$.visitorStackGraph = function({teen, twenties, thirties, forties, fifties, sixties}){
    const ctx4 = document.querySelector("#visitorStackGraph");
    
    const getLast7Days = () => {
        const today = new Date();
        const last7Days = Array.from({ length: 7 }, (_, i) => {
            const day = new Date(today);
            day.setDate(today.getDate() - i);
            const formattedDate = day.toISOString().slice(2, 10); // Format as 'YY-MM-DD'
            return formattedDate;
        });
        return last7Days.reverse();
    };
    
    const weekDays = getLast7Days();
    console.log(weekDays);
    
    var stackChart = new Chart(ctx4, {
        type: 'bar',
        data: {
            labels: weekDays,
            datasets: [
                {
                    label: '10대(명)',
                    data: teen
                },
                {
                    label: '20대(명)',
                    data: twenties
                },
                {
                    label: '30대(명)',
                    data: thirties
                },
                {
                    label: '40대(명)',
                    data: forties
                },
                {
                    label: '50대(명)',
                    data: fifties
                },
                {
                    label: '60대 이상(명)',
                    data: sixties
                },
            ]
        },
        options: {
            responsive: true, // Enable responsiveness
            maintainAspectRatio: false, // Allow aspect ratio to be adjusted
            scales: {
                x: {
                    stacked: true,
                },
                y: {
                    stacked: true
                }
            }
        }
    });
};

// CSS in JS 기법
// 원본 CSS 스타일을 건드리지 않고 특정 페이지에서만 스타일링을 변경해야 하는 경우 자주 사용하는 기법
var headerEl = $("header");
var logoTxt = $(".logoTxt");
var loginHello = $(".loginHello");
var mLnbBtnDiv = $(".mLnbBtn div");
var pclnbA = $(".pclnb .main>a");
var mlnbLogoTxt = $(".mlnb .logoTxt");
var mlnbLoginHello = $(".mlnb .loginHello");
var msubA = $(".msub a");
var gnbBtnGroupA = $(".gnbBtnGroup a");
var gnbBtnGroupI = $(".gnbBtnGroup i");
var mlnbGnbBtnGroupA = $(".mlnb .gnbBtnGroup a");
var mlnbGnbBtnGroupI = $(".mlnb .gnbBtnGroup i");

$.initHeaderStyle = function () {
    headerEl.removeAttr("style");
    logoTxt.removeAttr("style");
    loginHello.removeAttr("style");
    mLnbBtnDiv.removeAttr("style");
    pclnbA.removeAttr("style");
    mlnbLogoTxt.removeAttr("style");
    mlnbLoginHello.removeAttr("style");
    msubA.removeAttr("style");
    gnbBtnGroupA.removeAttr("style");
    gnbBtnGroupI.removeAttr("style");
    mlnbGnbBtnGroupA.removeAttr("style");
    mlnbGnbBtnGroupI.removeAttr("style");
};

$.startHeaderStyle = function () {
    headerEl.css({
        transition: "all 0.4s",
        backgroundColor: "rgba(0,0,0,0.6)"
    });
    logoTxt.css({
        color: "#eee"
    });
    mlnbLogoTxt.css({
        color: "#333"
    });
    loginHello.css({
        color: "#eee"
    });
    mlnbLoginHello.css({
        color: "#333"
    });
    pclnbA.css({
        color: "#eee"
    });
    mLnbBtnDiv.css({
        backgroundColor: "#eee"
    });
    msubA.css({
        color: "#333"
    });
    gnbBtnGroupA.css({
        backgroundColor: "#eee"
    });
    gnbBtnGroupI.css({
        color: "#333"
    });
    mlnbGnbBtnGroupA.css({
        backgroundColor: "#333"
    });
    mlnbGnbBtnGroupI.css({
        color: "#eee"
    });
    gnbBtnGroupA.mouseover(function () {
        var thisIs = $(this);
        thisIs.css({
            backgroundColor: "#333"
        });
        thisIs.find("i").css({
            color: "#eee"
        });
    });
    gnbBtnGroupA.mouseout(function () {
        var thisIs = $(this);
        thisIs.css({
            backgroundColor: "#eee"
        });
        thisIs.find("i").css({
            color: "#333"
        });
    });
    mlnbGnbBtnGroupA.mouseover(function () {
        var thisIs = $(this);
        thisIs.css({
            backgroundColor: "#eee"
        });
        thisIs.find("i").css({
            color: "#333"
        });
    });
    mlnbGnbBtnGroupA.mouseout(function () {
        var thisIs = $(this);
        thisIs.css({
            backgroundColor: "#333"
        });
        thisIs.find("i").css({
            color: "#eee"
        });
    });
};

$.scrollHeaderStyle = function () {
    headerEl.css({
        backgroundColor: "white"
    });
    logoTxt.css({
        color: "#333"
    });
    loginHello.css({
        color: "#333"
    });
    pclnbA.css({
        color: "#333"
    });
    mLnbBtnDiv.css({
        backgroundColor: "#333"
    });
    gnbBtnGroupA.css({
        backgroundColor: "black"
    });
    gnbBtnGroupI.css({
        color: "white"
    });
    gnbBtnGroupA.mouseover(function () {
        var thisIs = $(this);
        thisIs.css({
            backgroundColor: "#eee"
        });
        thisIs.find("i").css({
            color: "#333"
        });
    });
    gnbBtnGroupA.mouseout(function () {
        var thisIs = $(this);
        thisIs.css({
            backgroundColor: "#333"
        });
        thisIs.find("i").css({
            color: "#eee"
        });
    });
}

// 여행 정보 각각의 이미지 종횡비 변경 함수
$.eachJourneyInfoImgResizeFn = function(){
    $(".journeyInfoContents article").each(function(i, v){
        var thisIs = $(this);
        var infoThumbnailBox = thisIs.find(".infoThumbnailBox");
        var infoThumbnailImg = thisIs.find("img");
        $.ratioBoxH(infoThumbnailBox, infoThumbnailImg);
    });
};

$.startHeaderEmpty = function(){
    var winW = $(this).outerWidth();
    /* 가로길이 600px 이하일 때 헤더에 공백 주기 */
    var mainSlideContainer = $(".mainSlideContainer");
    if(winW < 600) {
        mainSlideContainer.addClass("emptySpace");
    }else {
        mainSlideContainer.removeClass("emptySpace");
    }
};

// 랜딩 페이지 > 여행 정보 실시간 검색 기능 함수
$.ajaxIndexJourneyInfoSearchFn = function(){
    var jourInfoSearch = $("#jourInfoSearch");
    jourInfoSearch.keyup(function(){
        var thisIs = $(this);
        var thisVal = thisIs.val();
        console.log("thisVal : ", thisVal);

        var languageType = detectLanguage(thisVal);
        console.log("languageType : " + languageType);

        var journeyVO = {};
        
        if(languageType == "korean") {
            journeyVO.infoName = thisVal;
            journeyVO.infoEngname = "";
        }else if(languageType == "english") {
            journeyVO.infoName = "";
            journeyVO.infoEngname = thisVal;
        }else {
            journeyVO.infoName = "";
            journeyVO.infoEngname = "";
        }

        $.ajax({
            type: "get",
            url: "/index/ajaxSearch.do",
            data: journeyVO,
            dataType: "json",
            success: function(res){
                console.log("res : ", res);

                var searchHtml = "";
                if(res.length == 0) {
                    searchHtml += `
                        <article style="text-align: center; width: 50%; margin: 0px auto; float: none; cursor: auto; background-color: #333; color: white; padding: 20px; border-radius: 4px;">
                            검색된 여행 정보가 없습니다.
                        </article>
                    `;
                }
                for(var i=0; i<res.length; i++){
                    searchHtml += `
                        <article>
                            <div class="infoThumbnailBox">
                                <img src="${res[i].infoPreviewimg}" alt="여행 정보 썸네일 이미지" />
                            </div>
                            <div>
                                <h4 class="textDrop">${res[i].infoEngname}</h4>
                                <span class="infoTitle textDrop">${res[i].infoName}</span>
                                <p>${res[i].infoDescription}</p>
                                <span class="airportText">`;

                            if(res[i].infoFlightyn == 'y'){
                                searchHtml += `<span>`;

                                if(res[i].infoFlight == "str"){
                                    searchHtml += `직항</span>`;
                                }else {
                                    searchHtml += `왕복</span>`;
                                }

                                searchHtml += `<span>${res[i].infoFlighttime}</span>`;
                            }else {
                                searchHtml += `<span>없음</span>
                                                <span>-</span>`;
                            }

                    searchHtml += `</span>
                                <span class="visaText">`;

                            if(res[i].infoVisayn == 'y'){
                                searchHtml += `<span>`;

                                if(res[i].infoVisaexp == "visa"){
                                    searchHtml += `비자</span>`;
                                }else {
                                    searchHtml += `무비자</span>`;
                                }

                                searchHtml += `<span>${res[i].infoVisatime}</span>`;
                            }else {
                                searchHtml += `<span>없음</span>
                                                <span>-</span>`;
                            }

                    searchHtml += `</span>
                                <span class="voltageTxt">
                                    <span>콘센트</span>
                                    <span>${res[i].infoVoltage}</span>
                                </span>
                                <span class="infoTimediferTxt">
                                    <span>한국대비</span>
                                    <span>${res[i].infoTimedifer}</span>
                                </span>
                                <input type="hidden" class="infoNo" name="infoNo" value="${res[i].infoNo}" />
                            </div>
                        </article>
                    `;
                }

                var journeyInfoContents = $(".journeyInfoContents");
                journeyInfoContents.html("");
                journeyInfoContents.append(searchHtml);

                $.JourneyInfoModalFn();
                $.eachJourneyInfoImgResizeFn();
            }
        });
    });
};

function detectLanguage(inputValue) {
    // 정규표현식을 사용하여 문자열이 한글인지 영어인지 판별
    var koreanRegex = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/;
    var englishRegex = /^[a-zA-Z]*$/;

    if (koreanRegex.test(inputValue)) {
        return "korean";
    } else if (englishRegex.test(inputValue)) {
        return "english";
    } else {
        return "etc";
    }
}

$.makeplanClickEvent = function(){
    var makePlanSty = $(".makePlanSty");
    makePlanSty.click(function(event){
        event.preventDefault();
        var thisIs = $(this);
        var hrefVal = thisIs.attr("href");
        var spanVal = thisIs.parents(".infoModalLeft").find(".modalInfoSetting>span").text();
        console.log("spanVal : " + spanVal);
        hrefVal += "?infoName=" + spanVal;
        console.log("hrefVal : " + hrefVal);
        location.href = hrefVal;
    });
};

// 윈도우 가로 길이 변경 이벤트
$(window).resize(function(){
    var winW = $(this).outerWidth();
    
    if(winW < 1060) {
        $.eachJourneyInfoImgResizeFn();
    }
    if(winW < 680) {
        $.eachJourneyInfoImgResizeFn();
    }
    
    $.startHeaderEmpty();
});

// 윈도우 스크롤 이벤트
$(window).scroll(function () {
    var scrD = $(this).scrollTop();
    console.log("현재 스크롤의 위치 : " + scrD + " px");

    // 스크롤 위치가 0px이면 herder를 투명하게, 아니면 하얀색 배경
    if (scrD == 0) {
        $.initHeaderStyle();
        $.startHeaderStyle();
    } else {
        $.scrollHeaderStyle();
    }
});

package kr.or.ddit;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import kr.or.ddit.indexsearch.service.IndexSearchService;
import kr.or.ddit.users.login.vo.MemberVO;
import kr.or.ddit.users.myplan.vo.JourneyinfoVO;
import kr.or.ddit.utils.ServiceResult;
import kr.or.ddit.vo.RealTimeSenderVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class HomeController {
	
	@Inject
	private IndexSearchService indexSearchService;
	
	// 랜딩 페이지
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home() {
		return "home";
	}
	
	// 랜딩 페이지 리다이렉트
	@CrossOrigin(origins = "http://localhost")
	@RequestMapping(value = "/index.do", method = RequestMethod.GET)
	public String index(Model model) {
		/** 자료 수집 및 정의 */
		Map<String, Object> param = new HashMap<String, Object>();
		
		/** 서비스 호출 */
		// 여행 정보 리스트(최신 글 순, 8개) 가져오기
		indexSearchService.informationList8(param);
		
		/** 반환 자료 */
		List<JourneyinfoVO> journeyList8 = (List<JourneyinfoVO>) param.get("journeyList8");
		
		/** 자료 검증 */
		log.info("journeyList8 => {}", journeyList8);
		
		/** 자료 반환 */
		model.addAttribute("journeyList8", journeyList8);
		
		return "user/index";
	}

	// 개인정보처리방침 페이지
	@CrossOrigin(origins = "http://localhost")
	@RequestMapping(value = "/personalInfo.do", method = RequestMethod.GET)
	public String personalInfo() {
		return "user/personalInfo";
	}
	
	// 영상정보처리기기 운영관리 방침 페이지
	@CrossOrigin(origins = "http://localhost")
	@RequestMapping(value = "/imageInfo.do", method = RequestMethod.GET)
	public String imageInfo() {
		return "user/imageInfo";
	}
	
	// ajax > 실시간 멤버 아이디 전체 리스트 가져오기
	@GetMapping("/membersIdGet.do")
	@ResponseBody
	public List<MemberVO> ajaxMembersId() {
		return indexSearchService.ajaxMembersId();
	}
	
	// ajax > 실시간 알림 > 내역 저장
	@PostMapping("/allMemberRtAlertSave.do")
	@ResponseBody
	public String ajaxRtAlert(RealTimeSenderVO realVO) {
		log.info("realVO : {}", realVO);
		
		/** 자료 수집 및 정의 */
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("realVO", realVO);
		
		/** 서비스 호출 */
		indexSearchService.ajaxRtAlert(param);
		
		/** 반환 자료 */
		ServiceResult result = (ServiceResult) param.get("result");
		String judge = "";
		
		if(result.equals(ServiceResult.OK)) {
			judge = "OK";
		}else {
			judge = "FAIL";
		}
		
		return judge;
	}
	
	// ajax > 실시간 알림 메시지를 전역으로 가져와 뿌리깅
	@GetMapping("/rtAlertGetMsg.do")
	@ResponseBody
	public Map<String, Object> ajaxRtSenderGetMsgFn(
			HttpSession session
			) {
		/** 자료 수집 및 정의 */
		Map<String, Object> param = new HashMap<String, Object>();
		Map<String, Object> resObj = new HashMap<String, Object>();
		
		MemberVO memberVO = (MemberVO) session.getAttribute("sessionInfo");
		if(memberVO != null) {
			param.put("memId", memberVO.getMemId());
		}
		
		/** 서비스 호출 */
		// 실시간 알림 메시지 가져오기
		indexSearchService.ajaxRtSenderGetMsgFn(param);
		
		/** 반환 자료 */
		RealTimeSenderVO journeySender = (RealTimeSenderVO) param.get("journeySender");
		int journeyCnt = (int) param.get("journeyCnt");
		
		/** 자료 검증 */
		log.info("journeySender : {}", journeySender);
		log.info("journeyCnt : {}", journeyCnt);
		
		/** 자료 반환 */
		resObj.put("journeySender", journeySender);
		resObj.put("journeyCnt", journeyCnt);
		return resObj;
	}
	
	// ajax > 바로 가기 클릭 시 안 본 실시간 알림을 본 것으로 처리
	@PostMapping("/readRtAlert.do")
	@ResponseBody
	public String rtAlertRead(@RequestBody String realrecNo) {
		log.info("realrecNo : {}", realrecNo);
		
		/** 자료 수집 및 정의 */
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("realrecNo", realrecNo);
		
		/** 서비스 호출 */
		indexSearchService.ajaxRtAlertRead(param);
		
		/** 반환 자료 */
		ServiceResult result = (ServiceResult) param.get("result");
		String judge = "";
		
		if(result.equals(ServiceResult.OK)) {
			judge = "OK";
		}else {
			judge = "FAIL";
		}
		
		return judge;
	}
	
	// ajax > 실시간 알림 > 동행 참가 요청 > 해당 플래너 찾기
	@GetMapping("/planDetailCreateMemId.do")
	@ResponseBody
	public MemberVO planDetailCreateMemId(String plNo) {
		log.info("plNo : {}", plNo);
		return indexSearchService.planDetailCreateMemId(plNo);
	}
	
	// ajax > 로그인할 멤버 정보 가져오기
	@GetMapping("/loginMemInfoRtAlertSaveInfo.do")
	@ResponseBody
	public MemberVO loginMemInfoRtAlertSaveInfo(
			MemberVO loginMemVO
			) {
		
		log.info("loginMemVO : {}", loginMemVO);
		return indexSearchService.loginMemInfoRtAlertSaveInfo(loginMemVO);
	
	}
	
	// ajax > 로그인 알림 1번 출력 이후 바로 삭제
	@PostMapping("/ajaxLoginRtAlertRemove.do")
	@ResponseBody
	public String ajaxLoginRtAlertRemove(
			HttpSession session
			) {
		
		/** 자료 수집 및 정의 */
		Map<String, Object> param = new HashMap<String, Object>();
		
		MemberVO memberVO = (MemberVO) session.getAttribute("sessionInfo");
		if(memberVO != null) {
			param.put("memId", memberVO.getMemId());
		}
		
		/** 서비스 호출 */
		indexSearchService.ajaxLoginRtAlertRemove(param);
		
		/** 반환 자료 */
		ServiceResult result = (ServiceResult) param.get("result");
		String judge = "";
		
		if(result.equals(ServiceResult.OK)) {
			judge = "OK";
		}else {
			judge = "FAIL";
		}
		
		return judge;
		
	}
	
	// ajax > 딸랑이 클릭하면 모두 읽음 처리
	@GetMapping("/rtAlertClickInit.do")
	@ResponseBody
	public String rtAlertClickInit(String memId) {
		log.info("memId : {}", memId);
		
		/** 자료 수집 및 정의 */
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("memId", memId);
		
		/** 서비스 호출 */
		indexSearchService.rtAlertClickInit(param);
		
		/** 반환 자료 */
		ServiceResult result = (ServiceResult) param.get("result");
		String judge = "";
		
		if(result.equals(ServiceResult.OK)) {
			judge = "OK";
		}else {
			judge = "FAIL";
		}
		
		return judge;
	}
	
}

package kr.or.ddit.indexsearch.vo;

import org.springframework.web.multipart.MultipartFile;

import lombok.Data;

@Data
public class JourneyinfoVO {

	private int infoNo;
	private String infoName;
	private String infoEngname;
	private String infoDescription;
	private String infoFlightyn;
	private String infoFlight;
	private String infoFlighttime;
	private String infoVisayn;
	private String infoVisaexp;
	private String infoVisatime;
	private String infoVoltage;
	private String infoTimedifer;
	private String infoRegdate;
	
	private MultipartFile imgFile;
	private String infoPreviewimg;
	
}

package kr.or.ddit.indexsearch.service;

import java.util.List;
import java.util.Map;

import kr.or.ddit.indexsearch.vo.JourneyinfoVO;
import kr.or.ddit.users.login.vo.MemberVO;

public interface IndexSearchService {

	public void informationList8(Map<String, Object> param);
	public List<JourneyinfoVO> searchJourneyList8(JourneyinfoVO journeyVO);
	public List<MemberVO> ajaxMembersId();
	public void ajaxRtAlert(Map<String, Object> param);
	public void ajaxRtSenderGetMsgFn(Map<String, Object> param);
	public void ajaxRtAlertRead(Map<String, Object> param);
	public MemberVO planDetailCreateMemId(String plNo);
	public MemberVO loginMemInfoRtAlertSaveInfo(MemberVO loginMemVO);
	public void ajaxLoginRtAlertRemove(Map<String, Object> param);
	public void rtAlertClickInit(Map<String, Object> param);

}

package kr.or.ddit.indexsearch.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import kr.or.ddit.indexsearch.service.IndexSearchService;
import kr.or.ddit.indexsearch.vo.JourneyinfoVO;
import kr.or.ddit.mapper.IndexSearchMapper;
import kr.or.ddit.users.login.vo.MemberVO;
import kr.or.ddit.utils.ServiceResult;
import kr.or.ddit.vo.RealTimeSenderVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class IndexSearchServiceImpl implements IndexSearchService {

	@Inject
	private IndexSearchMapper indexSearchMapper;
	
	@Override
	public void informationList8(Map<String, Object> param) {
		/** 파라미터 조회 */
		/** 파라미터 정의 */
		List<JourneyinfoVO> journeyList8 = new ArrayList<JourneyinfoVO>();
		
		/** 메인로직 처리 */
		journeyList8 = indexSearchMapper.informationList8();
		
		/** 반환자료 저장 */
		param.put("journeyList8", journeyList8);
	}

	@Override
	public List<JourneyinfoVO> searchJourneyList8(JourneyinfoVO journeyVO) {
		return indexSearchMapper.searchJourneyList8(journeyVO);
	}

	@Override
	public List<MemberVO> ajaxMembersId() {
		return indexSearchMapper.ajaxMembersId();
	}

	@Override
	public void ajaxRtAlert(Map<String, Object> param) {
		/** 파라미터 조회 */
		RealTimeSenderVO realVO = (RealTimeSenderVO) param.get("realVO");
		
		/** 파라미터 정의 */
		ServiceResult result = null;
		int status = 0;
		
		String[] realrecIdArr = null;
		
		/** 메인로직 처리 */
		// ajax > 실시간 알림 > 로그인 내역 저장
		// 1. 가져온 로그 데이터를 발신자 테이블에 1번만 insert 해보아요
		status = indexSearchMapper.insertSender(realVO);
		
		if(status > 0) { // 마더 값이 들어감
			// 2. 수신자 테이블에 수신자 멤버 수 만큼 insert 해 보아요
			realrecIdArr = realVO.getRealrecIdArr();
			for(String recId : realrecIdArr) {
				realVO.setRealrecId(recId);
				indexSearchMapper.insertReceiver(realVO);
			}
			result = ServiceResult.OK;
		}else { // 실패
			result = ServiceResult.FAILED;
		}
		
		/** 반환자료 저장 */
		param.put("result", result);
	}

	@Override
	public void ajaxRtSenderGetMsgFn(Map<String, Object> param) {
		/** 파라미터 조회 */
		String memId = (String) param.get("memId");
		
		/** 파라미터 정의 */
		RealTimeSenderVO journeySender = new RealTimeSenderVO();
		int journeyCnt = 0;
		
		/** 메인로직 처리 */
		// 실시간 알림 정보를 가져오기(로그인한 세션 기준)
		journeySender = indexSearchMapper.sender(memId);
		// 실시간 알림 정보 갯수 가져오기(로그인한 세션 기준)
		journeyCnt = indexSearchMapper.senderCnt(memId);
		
		/** 반환자료 저장 */
		param.put("journeySender", journeySender);
		param.put("journeyCnt", journeyCnt);
	}

	@Override
	public void ajaxRtAlertRead(Map<String, Object> param) {
		/** 파라미터 조회 */
		int realrecNo = Integer.parseInt((String) param.get("realrecNo"));
		log.info("ajaxRtAlertRead > realrecNo : {}", realrecNo);
		
		/** 파라미터 정의 */
		int status = 0;
		ServiceResult result = null;
		
		/** 메인로직 처리 */
		// 바로 가기 클릭 시 안 본 실시간 알림을 본 것으로 처리해 보아요. 업데이트여요.
		status = indexSearchMapper.ajaxRtAlertRead(realrecNo);
		log.info("ajaxRtAlertRead > status : {}", status);
		
		if(status > 0) {
			result = ServiceResult.OK;
		}else { // 실패
			result = ServiceResult.FAILED;
		}
		
		/** 반환자료 저장 */
		param.put("result", result);
	}

	@Override
	public MemberVO planDetailCreateMemId(String plNo) {
		int intPlNo = Integer.parseInt(plNo);
		return indexSearchMapper.planDetailCreateMemId(intPlNo);
	}

	@Override
	public MemberVO loginMemInfoRtAlertSaveInfo(MemberVO loginMemVO) {
		return indexSearchMapper.loginMemInfoRtAlertSaveInfo(loginMemVO);
	}

	@Override
	public void ajaxLoginRtAlertRemove(Map<String, Object> param) {
		/** 파라미터 조회 */
		String memId = (String) param.get("memId");
		
		/** 파라미터 정의 */
		Map<String, Object> paramMap = new HashMap<String, Object>();
		ServiceResult result = null;
		
		int cnt = 0;
		RealTimeSenderVO rtSender = new RealTimeSenderVO();
		int realsenNo = 0;
		int status = 0;
		int status2 = 0;
		
		/** 메인로직 처리 */
		// 0. 일단 로그인 쌓인게 몇 건인지 가져와 봐요
		cnt = indexSearchMapper.removeRealSenNoCnt();
		log.info("cnt : {}", cnt);
		
		if(cnt > 0) { // 1건 이상 있다는 말이므로 cnt 만큼 반복문을 돌려서 삭제해요
			
			for(int i = 0; i < cnt; i++) {
				
				// 1. 로그인한 알림의 realsenNo 값을 가져와요
				rtSender = indexSearchMapper.removeSelectRealSenNo(memId);
				
				// 1-1. 예외 처리: removeSelectRealSenNo가 null을 반환하는 경우
				if (rtSender == null) {
				    log.warn("removeSelectRealSenNo returned null. Handle this case accordingly.");
				    // 처리할 내용을 여기에 추가하세요.
				    // 예를 들어, 오류 응답을 클라이언트로 반환하거나 기본값으로 초기화할 수 있습니다.
				    realsenNo = 0; // 기본값으로 초기화 예시
				}else {
					realsenNo = rtSender.getRealsenNo();
				}

				log.info("realsenNo : {}", realsenNo);
				
				// 2. 조회한 realsenNo 값으로 발신자 테이블을 삭제해요
				status = indexSearchMapper.removeRealtimereceiverTbl(realsenNo);
				log.info("status : {}", status);
				
				if(status > 0) {
					// 3. 조회한 realsenNo 값으로 수신자 테이블을 삭제해요
					status2 = indexSearchMapper.removeRealtimesenderTbl(realsenNo);
					log.info("status2 : {}", status2);
					if(status2 > 0) {
						result = ServiceResult.OK;
					}else {
						result = ServiceResult.FAILED;
					}
				}else { // 실패
					result = ServiceResult.FAILED;
				}
				
			}
			
		}else { // 돌릴게 없으므로 그냥 ok 사인을 던져요
			result = ServiceResult.OK;
		}
		
		/** 반환자료 저장 */
		param.put("result", result);
	}

	@Override
	public void rtAlertClickInit(Map<String, Object> param) {
		/** 파라미터 조회 */
		String memId = (String) param.get("memId");
		
		/** 파라미터 정의 */
		ServiceResult result = null;
		int status = 0;
		
		/** 메인로직 처리 */
		status = indexSearchMapper.rtAlertClickInit(memId);
		
		if(status > 0) { // 실시간 알림 초기화 완료
			result = ServiceResult.OK;
		}else { // 실시간 알림 초기화 실패
			result = ServiceResult.FAILED;
		}
		
		/** 반환자료 저장 */
		param.put("result", result);
	}

}

package kr.or.ddit.mapper;

import java.util.List;

import kr.or.ddit.indexsearch.vo.JourneyinfoVO;
import kr.or.ddit.users.login.vo.MemberVO;
import kr.or.ddit.vo.RealTimeSenderVO;

public interface IndexSearchMapper {

	public List<JourneyinfoVO> informationList8();
	public List<JourneyinfoVO> searchJourneyList8(JourneyinfoVO journeyVO);
	public List<MemberVO> ajaxMembersId();
	public int insertSender(RealTimeSenderVO realVO);
	public void insertReceiver(RealTimeSenderVO rtAlertVO);
	public RealTimeSenderVO sender(String memId);
	public int senderCnt(String memId);
	public int ajaxRtAlertRead(int intRealrecNo);
	public MemberVO planDetailCreateMemId(int intPlNo);
	public MemberVO loginMemInfoRtAlertSaveInfo(MemberVO loginMemVO);
	
	public int removeRealSenNoCnt();
	public RealTimeSenderVO removeSelectRealSenNo(String memId);
	public int removeRealtimesenderTbl(int realsenNo);
	public int removeRealtimereceiverTbl(int realsenNo);
	
	public int rtAlertClickInit(String memId);

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.or.ddit.mapper.IndexSearchMapper">

	<select id="informationList8" resultType="indexsearchVO">
		SELECT *
		FROM (
		    SELECT
		        info_no,
		        info_name,
		        info_engname,
		        info_description,
		        info_flightyn,
		        info_flight,
		        info_flighttime,
		        info_visayn,
		        info_visaexp,
		        info_visatime,
		        info_voltage,
		        info_timedifer,
		        info_previewimg,
		        info_regdate
		    FROM
		        journeyinfo
		    ORDER BY info_no DESC
		)
		<![CDATA[
			WHERE ROWNUM <= 8
		]]>
	</select>
	
	<select id="searchJourneyList8" parameterType="indexsearchVO" resultType="indexsearchVO">
		SELECT *
		FROM (
		    SELECT *
			FROM journeyinfo
			WHERE info_name LIKE '%' || #{infoName} || '%'
			   AND UPPER(info_engname) LIKE UPPER('%' || #{infoEngname} || '%')
			ORDER BY info_no desc
		)
		<![CDATA[
			WHERE ROWNUM <= 8
		]]>
	</select>
	
	<select id="ajaxMembersId" resultType="memberVO">
		select mem_id
		  from members
	</select>
	
	<insert id="insertSender" parameterType="rtsenderVO">
		<selectKey keyProperty="realsenNo" order="BEFORE" resultType="int">
			SELECT  
				seq_realtimesender.nextval
			FROM 
				dual
		</selectKey>
		INSERT INTO realtimesender (
		    realsen_no,
		    realsen_id,
		    realsen_name,
		    realsen_title,
		    realsen_content,
		    realsen_type,
		    realsen_url,
		    realsen_pfimg
		) VALUES (
		    #{realsenNo},
		    #{realsenId},
		    #{realsenName},
		    #{realsenTitle},
		    #{realsenContent},
		    #{realsenType},
		    #{realsenUrl},
		    #{realsenPfimg}
		)
	</insert>
	
	<insert id="insertReceiver" parameterType="rtsenderVO">
		INSERT INTO realtimereceiver (
		    realrec_no,
		    realrec_id,
		    realsen_readyn,
		    realsen_no
		) VALUES (
		    seq_realtimereceiver.nextval,
		    #{realrecId},
		    #{realsenReadyn},
		    #{realsenNo}
		)
	</insert>
	
	<select id="sender" parameterType="String" resultType="rtsenderVO">
		select *
		  from (
		    select a.*
		         , b.realrec_no
		         , b.realrec_id
		         , b.realsen_readyn
		      from realtimesender a
		         , realtimereceiver b
		     where 1=1
		       and a.realsen_no = b.realsen_no
		       and b.realsen_readyn = 'N'
		       and b.realrec_id = 'chantest1'
		       order by a.realsen_no desc
		  ) a
		 where 1 = 1
		   and ROWNUM = 1
	</select>
	
	<select id="senderCnt" parameterType="String" resultType="int">
		select count(*) as journeyCnt
		  from realtimesender a
		     , realtimereceiver b
		 where 1=1
		   and a.realsen_no = b.realsen_no
		   and b.realsen_readyn = 'N'
		   and b.realrec_id = #{realrecId}
	</select>
	
	<update id="ajaxRtAlertRead" parameterType="int">
		UPDATE realtimereceiver
		SET
		    realsen_readyn = 'Y'
		WHERE
		    realrec_no = #{realrecNo}
	</update>
	
	<select id="planDetailCreateMemId" parameterType="int" resultType="memberVO">
		select * 
		  from planer 
		 where 1=1
		   and pl_no = #{intPlNo}
	</select>
	
	<select id="loginMemInfoRtAlertSaveInfo" parameterType="memberVO" resultType="memberVO">
		select *
		  from members
		 where 1=1 
		   and mem_id = #{memId}
		   and mem_pw = #{memPw}
	</select>
	
	<select id="removeSelectRealSenNo" parameterType="String" resultType="rtsenderVO">
		select *
		  from (
		      select realsen_no 
		      from realtimesender
		     where 1=1
		       and realsen_id = #{memId}
		       and realsen_type = 'logininfo'
		     order by realsen_no desc
		  ) a
		 where 1=1
		   and rownum = 1
	</select>
	
	<delete id="removeRealtimesenderTbl" parameterType="int">
		DELETE FROM realtimesender
		WHERE realsen_no = #{realsenNo}
	</delete>
	
	<delete id="removeRealtimereceiverTbl" parameterType="int">
		DELETE FROM realtimereceiver
		WHERE realsen_no = #{realsenNo}
	</delete>
	
	<select id="removeRealSenNoCnt" resultType="int">
		select count(*) as cnt
		  from realtimesender
		 where 1=1
		   and realsen_type = 'logininfo'
	</select>
	
	<update id="rtAlertClickInit" parameterType="String">
		 UPDATE realtimereceiver b
			SET b.realsen_readyn = 'Y'
			WHERE EXISTS (
			    SELECT 1
			    FROM realtimesender a
			    WHERE a.realsen_no = b.realsen_no
			      AND b.realsen_readyn = 'N'
			      AND b.realrec_id = #{memId}
			)
	</update>

</mapper>