(() => {
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();
})();