(() => { const WEEKDAY = ["일", "월", "화", "수", "목", "금", "토"]; const MOCK_EXAM_DT = 20261015; const API_BASE_URL = `${DSDOENV.ApiConfig.DSDO_API}/api/v1/on-site-exams`; // 접수 시작일, 접수 종료일, 성적 확인일 js에서 관리 - 응시일은 api로 받아온 데이터로 출력 const APPLY_START_DATE = new Date(2026, 8, 3); const APPLY_END_DATE = new Date(2026, 9, 5); const SCORE_CHECK_DATE = new Date(2026, 9, 26); const ddayEl = document.getElementById("rehearsal-dday"); const timeEl = document.getElementById("rehearsal-time"); const examNameEl = document.getElementById("rehearsal-exam-name"); const examYearEl = document.getElementById("rehearsal-exam-year"); const examDateEl = document.getElementById("rehearsal-exam-date"); const visualExamDateEl = document.getElementById("rehearsal-visual-exam-date"); const scheduleApplyDateEl = document.getElementById("rehearsal-schedule-apply-date"); const scheduleExamDateEl = document.getElementById("rehearsal-schedule-exam-date"); const scheduleScoreDateEl = document.getElementById("rehearsal-schedule-score-date"); const periodDateEl = document.getElementById("rehearsal-period-date"); const applyBannerBtnEl = document.getElementById("rehearsal-apply-banner-btn"); const applyBannerEl = document.getElementById("rehearsal-apply-banner"); const applyBannerClosedEl = document.getElementById("rehearsal-apply-banner-closed"); const tabItems = document.querySelectorAll(".rehearsal-tab__item[data-tab]"); const tabContents = document.querySelectorAll("[data-tab-content]"); let currentMockExamId = null; let OnSiteExamProductNo1 = null; let OnSiteExamProductNo2 = null; let shopbyCommonData = {}; const getCookie = (name) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(";").shift(); return null; }; const pad = (num) => String(num).padStart(2, "0"); const formatSlashDate = (date) => `${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())}(${WEEKDAY[date.getDay()]})`; scheduleApplyDateEl.textContent = `${formatSlashDate(APPLY_START_DATE)} ~ ${formatSlashDate(APPLY_END_DATE)}`; scheduleScoreDateEl.textContent = formatSlashDate(SCORE_CHECK_DATE); periodDateEl.textContent = `${formatSlashDate(APPLY_START_DATE)} ~ ${formatSlashDate(APPLY_END_DATE)}`; const startCountdown = (targetDate) => { const updateCountdown = () => { const diff = targetDate - new Date(); if (diff <= 0) { ddayEl.textContent = "D-DAY"; timeEl.textContent = "00:00:00"; clearInterval(timer); return; } const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff / (1000 * 60 * 60)) % 24); const minutes = Math.floor((diff / (1000 * 60)) % 60); const seconds = Math.floor((diff / 1000) % 60); ddayEl.textContent = `D-${days}`; timeEl.textContent = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; }; const timer = setInterval(updateCountdown, 1000); updateCountdown(); }; const renderExam = (exam) => { currentMockExamId = exam.mockExamId; OnSiteExamProductNo1 = exam.onSiteExamProductNo1; OnSiteExamProductNo2 = exam.onSiteExamProductNo2; const examDate = new Date(exam.examDate); const dateText = `${examDate.getMonth() + 1}월 ${examDate.getDate()}일 ${WEEKDAY[examDate.getDay()]}요일`; examNameEl.textContent = exam.examName; examYearEl.textContent = `${examDate.getFullYear()}년`; examDateEl.textContent = dateText; visualExamDateEl.textContent = dateText; scheduleExamDateEl.textContent = formatSlashDate(examDate); applyBannerBtnEl.href = `/pages/rehearsal/product-detail.html?productNo=${exam.onSiteExamProductNo2}`; // 시험 시작 시각(오전 8시 10분)으로 카운트다운 타겟 고정 const examStartDate = new Date(examDate); examStartDate.setHours(8, 10, 0, 0); startCountdown(examStartDate); }; const fetchExam = async () => { const url = `${API_BASE_URL}/mock-exam-dt/${MOCK_EXAM_DT}`; const response = await fetch(url); if (!response.ok) throw new Error(`현장 모의고사 조회 실패 (${response.status})`); const {data} = await response.json(); return data[0]; }; const fetchApplicationAvailability = async (mockExamId) => { const url = `${API_BASE_URL}/applications/availability?mockExamId=${mockExamId}`; const response = await fetch(url); if (!response.ok) throw new Error(`신청 가능 여부 조회 실패 (${response.status})`); const {data} = await response.json(); return data; }; const renderApplyBanner = ({closed}) => { applyBannerEl.hidden = closed; applyBannerClosedEl.hidden = !closed; }; const redirectLoginPage = () => { location.href = `${location.origin}/pages/sign-in/sign-in.html?from=${encodeURIComponent(location.href)}`; }; const fetchMyApplications = async ({mockExamId, memberNo}) => { const url = `${API_BASE_URL}/applications?${new URLSearchParams({ mockExamId, memberNo })}`; const response = await fetch(url); if (response.status === 404) return null; if (!response.ok) { throw new Error('THE PREMIUM 수능 리허설을 신청할 수 없습니다. 오류가 계속 될 경우 관리자에게 문의주세요.'); } const {data} = await response.json(); return data; }; const checkOctoberMockExam = async (memberNo) => { if(!memberNo) return; try { const response = await fetch( `${API_BASE_URL}/orders/october-mock-exam/exists?memberNo=${memberNo}` ); if(!response.ok) { throw new Error('THE PREMIUM 구매 이력 조회에 실패했습니다.'); } const result = await response.json(); if(!result.data) { throw new Error('THE PREMIUM 구매 이력을 찾을 수 없습니다.'); } return result.data.exists; } catch(e) { console.error(e); //location.href = '/'; return null; } } const openInquiryModal = () => { ShopbySkin.EventManager.fire("OPEN_LAYER_MODAL", { name: "personal-inquiry-form", title: "1:1문의 등록", modalAddClass: "personal-inquiry-form-modal", isFull: false, data: { isModify: false, commonData: shopbyCommonData, }, onClose: ({reason}) => { if (reason === "DID_SUBMIT") { ShopbySkin.EventManager.fire("MODAL_ALERT_OPEN", { noticeType: "SUCCESS", message: "게시글이 저장되었습니다.", onClose: () => { location.replace(location.pathname); }, }); } }, }); }; const moduleActionHandler = { SWITCH_TAB: (target) => { const activeTab = target.dataset.tab; tabItems.forEach((item) => item.classList.toggle("is-active", item === target)); tabContents.forEach((content) => { content.hidden = content.dataset.tabContent !== activeTab; }); }, OPEN_TERM_MODAL: (target) => { ShopbySkin.EventManager.fire("OPEN_LAYER_MODAL", {name: target.dataset.openModal}); }, APPLICATION_CLOSED: () => { ShopbySkin.EventManager.fire("MODAL_ALERT_OPEN", { noticeType: "CAUTION", message: "준비된 모집 정원 마감되었습니다.
취소자 발생 시 신청이 재오픈될 수 있습니다.
", }); }, OPEN_INQUIRY_FORM: () => { if (!ShopbySkin.utils.isSignIn()) { ShopbySkin.EventManager.fire("MODAL_CONFIRM_OPEN", { message: "로그인 후 이용할 수 있습니다.", onConfirm: () => { const returnUrl = new URL(location.href); returnUrl.searchParams.set("openInquiry", "1"); location.href = `${location.origin}/pages/sign-in/sign-in.html?from=${encodeURIComponent(returnUrl.href)}`; }, }); return; } openInquiryModal(); }, OPEN_APPLY: async (target) => { if (!ShopbySkin.utils.isSignIn()) { ShopbySkin.EventManager.fire("MODAL_CONFIRM_OPEN", { message: "로그인 후 이용할 수 있습니다.", onConfirm: () => { location.href = `${location.origin}/pages/sign-in/sign-in.html?from=${encodeURIComponent(location.href)}`; }, }); return; } const memberNo = getCookie("memberNo"); const applications = await fetchMyApplications({mockExamId: currentMockExamId, memberNo}); if (applications && applications?.applicationStatus !== 'CANCELED') { ShopbySkin.EventManager.fire("MODAL_ALERT_OPEN", { noticeType: "CAUTION", message: "이미 신청이 완료되었습니다.", label: "신청내역 확인", onClose: () => { moduleActionHandler.CHECK_APPLICATION(); }, }); return; } const isOctoberMockExam = await checkOctoberMockExam(memberNo); const productNo = isOctoberMockExam ? OnSiteExamProductNo2 : OnSiteExamProductNo1; location.href = `/pages/rehearsal/product-detail.html?productNo=${productNo}`; }, CHECK_APPLICATION: async () => { if (!ShopbySkin.utils.isSignIn()) { ShopbySkin.EventManager.fire("MODAL_ALERT_OPEN", { noticeType: "CAUTION", message: "로그인이 필요합니다.", onClose: redirectLoginPage, }); return; } const memberNo = getCookie("memberNo"); const applications = await fetchMyApplications({mockExamId: currentMockExamId, memberNo}); if (!applications) { ShopbySkin.EventManager.fire("MODAL_ALERT_OPEN", { noticeType: "CAUTION", message: "신청 내역이 없습니다." }); return; } location.href = `/pages/rehearsal/application-detail.html?applicationId=${applications.id}`; }, }; // 초기화 코드 document.addEventListener("click", (event) => { const target = event.target.closest("[shopby-action]"); if (!target) return; if (target.tagName === "A") event.preventDefault(); moduleActionHandler[target.getAttribute("shopby-action")]?.(target); }); ShopbySkin.EventManager.on("PAGE_LOAD_COMPLETED", (commonData) => { shopbyCommonData = commonData; // 로그인 페이지에서 되돌아온 경우 자동으로 문의 모달 오픈 const url = new URL(location.href); if (url.searchParams.get("openInquiry") !== "1") return; url.searchParams.delete("openInquiry"); history.replaceState(null, "", url); openInquiryModal(); }); (async () => { try { const exam = await fetchExam(); renderExam(exam); const availability = await fetchApplicationAvailability(exam.mockExamId); renderApplyBanner(availability); } catch (error) { console.error(error); ShopbySkin.EventManager.fire("MODAL_ALERT_OPEN", { noticeType: "CAUTION", message: "THE PREMIUM 수능 리허설 페이지에 접근할 수 없습니다.
관리자에게 문의해주세요.
", onClose: () => { location.href = "/"; }, }); } })(); // 대청중학교 위치 const EXAM_SITE_POSITION = {lat: 37.4905059032003, lng: 127.058315194339}; const mapContainer = document.getElementById("rehearsal-map-canvas"); const center = new kakao.maps.LatLng(EXAM_SITE_POSITION.lat, EXAM_SITE_POSITION.lng); const map = new kakao.maps.Map(mapContainer, {center, level: 3}); map.setZoomable(false); new kakao.maps.Marker({map, position: center}); const EXAM_SITE_KAKAO_MAP_URL = `https://map.kakao.com/link/to/${encodeURIComponent("대청중학교")},37.4939,127.0628`; kakao.maps.event.addListener(map, "click", () => { window.open(EXAM_SITE_KAKAO_MAP_URL, "_blank", "noopener,noreferrer"); }); const updateBannerPosition = () => { const footerEl = document.querySelector(".footer"); if (!footerEl) return; const overlap = window.innerHeight - footerEl.getBoundingClientRect().top; const offset = overlap > 0 ? overlap : 0; [applyBannerEl, applyBannerClosedEl].forEach((banner) => { if (banner) banner.style.bottom = `${offset}px`; }); const floatingEl = document.querySelector(".rehearsal-floating"); if (floatingEl) floatingEl.style.bottom = `${offset + 120}px`; }; window.addEventListener("scroll", updateBannerPosition, { passive: true }); window.addEventListener("resize", updateBannerPosition); updateBannerPosition(); })();