(() => { const API_BASE_URL = `${DSDOENV.ApiConfig.DSDO_API}/api/v1/on-site-exam-academies`; const SIMPLE_DAYS = ['일', '월', '화', '수', '목', '금', '토']; const APPLICATION_STATUS_LABELS = { WAITING: '접수 대기', OPEN: '접수중', CLOSED: '접수 마감', } const APPLICATION_STATUS_LABEL_COLORS = { WAITING: '', OPEN: ' blue', CLOSED: ' red', } const APPLICATION_TYPE_LABELS = { ACADEMY_INQUIRY: '학원 문의', ONLINE: '온라인 접수', } const APPLICATION_TYPE_LABEL_COLORS = { ACADEMY_INQUIRY: ' black_line', ONLINE: ' mint_line', } const DEALER_REGION_NAMES = { 1: "서울", 2: "광역시", 3: "경기", 4: "강원", 5: "충북", 6: "충남", 7: "전북", 8: "전남", 9: "경북", 10: "경남", 11: "제주", }; const KOREA_BOUNDS = { minLat: 33.10, maxLat: 38.63, minLng: 124.62, maxLng: 131.87 }; const MAP_DEFAULT_CENTER = { lat: 37.512897, lng: 126.9394209, } const ACADEMY_PAGE_SIZE = 10; const { Handlebars } = ShopbySkin; let academies = []; let academyPage = 0; let dealers = []; let dealerPage = 0; let map = null; let marker = null; const formatPhoneNumber = (phoneNumber) => { if(!phoneNumber) return ''; const formatted = phoneNumber.replace(/[^0-9]/g, ''); if(formatted.startsWith('02')) return formatted.replace(/(\d{2})(.*)(\d{4})/, '$1-$2-$3'); if(formatted.startsWith('050')) return formatted.replace(/(\d{4})(.*)(\d{4})/, '$1-$2-$3'); return formatted.replace(/(\d{3})(.*)(\d{4})/, '$1-$2-$3'); } const formatDate = (date) => { const dateObj = new Date(date); return `${dateObj.getMonth() + 1}/${dateObj.getDate()}(${SIMPLE_DAYS[dateObj.getDay()]})`; } const formatFullDate = (date) => { const dateObj = new Date(date); return `${dateObj.getFullYear()}. ${dateObj.getMonth() + 1}. ${dateObj.getDate()}.(${SIMPLE_DAYS[dateObj.getDay()]})`; } Handlebars.registerHelper('formatPhoneNumber', formatPhoneNumber); Handlebars.registerHelper('formatDate', formatDate); Handlebars.registerHelper('formatFullDate', formatFullDate); const isInKorea = (lat, lng) => { return ( lat >= KOREA_BOUNDS.minLat && lat <= KOREA_BOUNDS.maxLat && lng >= KOREA_BOUNDS.minLng && lng <= KOREA_BOUNDS.maxLng ); } const initMap = () => { const center = new kakao.maps.LatLng(MAP_DEFAULT_CENTER.lat, MAP_DEFAULT_CENTER.lng); const mapContainer = document.querySelector('#academy-map-container'); map = new kakao.maps.Map(mapContainer, { center, level: 3 }); map.setDraggable(false); marker = new kakao.maps.Marker({ map, position: center, }); } const getDealerList = async () => { try { const response = await fetch(`${DSDOENV.ApiConfig.DSDO_API}/api/v1/post/dealer?page=0&size=100`); if(!response.ok) throw new Error('Failed to fetch dealer list'); const data = await response.json(); return data?.content || []; } catch (e) { console.error(e); return []; } } const initDealerList = async () => { dealers = await getDealerList(); const target = document.querySelector( 'script[type="text/x-handlebars-template"]#dealer-template' ); if(!target) return; const source = target.innerHTML; const template = Handlebars.compile(source); dealerPage = 0; renderDealers(target, template); } const getOnsiteExamAcademyList = async ({ name, applicationOpen, sido, sigungu }) => { const params = new URLSearchParams(); if(name) params.append('name', name); if(applicationOpen && applicationOpen === 'true') params.append('applicationOpen', true); if(sido && sido !== '전체') params.append('sido', sido); if(sigungu && sigungu !== '전체') params.append('sigungu', sigungu); try { const response = await fetch(params.size ? `${API_BASE_URL}?${params.toString()}` : API_BASE_URL); if(!response.ok) throw new Error('Failed to fetch academy list'); const data = await response.json(); return data?.data || null; } catch (e) { console.error(e); return null; } } const fetchOnsiteExamAcademyList = async () => { const searchParams = new URLSearchParams(location.search); const name = searchParams.get('name') || ""; const applicationOpen = searchParams.get('applicationOpen') || undefined; const sido = searchParams.get('province') || ""; const sigungu = searchParams.get('district') || ""; const data = await getOnsiteExamAcademyList({ name, applicationOpen, sido, sigungu }); const container = document.querySelector('.academy_list .container'); if(!data || !data.visible) { const empty = container.firstElementChild; empty.classList.remove('hidden'); while(empty.nextElementSibling) { empty.nextElementSibling.remove(); } return; } const examInfoTarget = document.querySelector( 'script[type="text/x-handlebars-template"]#exam-info' ); const examInfoSource = examInfoTarget.innerHTML; const examInfoTemplate = Handlebars.compile(examInfoSource); const formattedExamDate = String(data.mockExam.mockExamDt) .replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'); const examDate = new Date(formattedExamDate); const examInfo = { ...data.mockExam, year: examDate.getFullYear(), month: examDate.getMonth() + 1, mockExamDt: formattedExamDate, closed: data.closed, }; const examInfoHtml = examInfoTemplate(examInfo); while (examInfoTarget.nextElementSibling) { examInfoTarget.nextElementSibling.remove(); } examInfoTarget.parentElement.insertAdjacentHTML('beforeend', examInfoHtml); if (data.closed) { container.querySelectorAll('section').forEach(section => section.remove()); container.lastElementChild.classList.remove('hidden'); return; } const target = document.querySelector( 'script[type="text/x-handlebars-template"]#academy-template' ); const source = target.innerHTML; const template = Handlebars.compile(source); academies = data.academies.map(academy => ({ ...academy, applicationStatusLabel: APPLICATION_STATUS_LABELS[academy.applicationStatus], applicationStatusLabelColor: APPLICATION_STATUS_LABEL_COLORS[academy.applicationStatus], applicationTypeLabel: APPLICATION_TYPE_LABELS[academy.applicationType], applicationTypeLabelColor: academy.applicationType === 'ONLINE' && academy.applicationStatus !== 'OPEN' ? ' disabled' : APPLICATION_TYPE_LABEL_COLORS[academy.applicationType], region: `${academy.address1.split(' ')[0]} ${academy.address1.split(' ')[1] ?? ''}`, })); academyPage = 0; while (target.nextElementSibling) { if(target.nextElementSibling.type === 'button') break; target.nextElementSibling.remove(); } renderAcademies(target, template); }; const renderAcademies = (target, template) => { const searchParams = new URLSearchParams(location.search); const name = searchParams.get('name') || ""; const applicationOpen = searchParams.get('applicationOpen') || undefined; const sido = searchParams.get('province') || ""; const start = academyPage * ACADEMY_PAGE_SIZE; const end = start + ACADEMY_PAGE_SIZE; const academyData = academies.slice(start, end); if (academies.length !== 0 && academyData.length === 0) { return; } const html = template({ academies: !!sido && sido !== '전체' && !name && !applicationOpen && !academies.length ? null : academyData, }); target.parentElement.lastElementChild.insertAdjacentHTML('beforebegin', html); academyPage++; const moreButton = document.querySelector('[data-action="SHOW_MORE_ACADEMY"]'); if (moreButton) { moreButton.style.display = end >= academies.length ? 'none' : 'block'; } }; const renderDealers = (target, template) => { const start = dealerPage * ACADEMY_PAGE_SIZE; const end = start + ACADEMY_PAGE_SIZE; const dealerData = dealers.slice(start, end); if (dealerData.length === 0) { return; } const html = template({ dealers: dealerData.map(dealer => ({ ...dealer, dealerRegion: DEALER_REGION_NAMES[dealer.dealerRegion], })) }); target.parentElement.insertAdjacentHTML('beforeend', html); dealerPage++; const moreButton = document.querySelector('[data-action="SHOW_MORE_DEALER"]'); if (moreButton) { moreButton.style.display = end >= dealers.length ? 'none' : 'block'; } }; const getProvinceList = async () => { try { const response = await fetch(`${API_BASE_URL}/regions/sidos`); if(!response.ok) throw new Error('Failed to fetch province list'); const data = await response.json(); return data?.data || []; } catch (e) { console.error(e); return []; } } const getDistrictList = async (province) => { if(!province) return; try { const response = await fetch(`${API_BASE_URL}/regions/sigungus?sido=${province}`); if(!response.ok) throw new Error('Failed to fetch district list'); const data = await response.json(); return data?.data || []; } catch (e) { console.error(e); return []; } } const fetchDistrictList = async () => { const searchParams = new URLSearchParams(location.search); const sido = searchParams.get('province') || ""; const sigungu = searchParams.get('district') || ""; const selectedDistrict = sido === '전체' ? '전체' : sigungu; const data = { districts: [], selectedValue: selectedDistrict }; const districtElem = document.querySelector('#district'); const target = document.querySelector( 'script[type="text/x-handlebars-template"]#district-template' ); const source = target.innerHTML; const template = Handlebars.compile(source); if(sido && sido !== '전체') { const districtList = await getDistrictList(sido); data.districts = districtList; } districtElem.value = selectedDistrict; districtElem.parentElement.disabled = !sido; const html = template(data); while (target.nextElementSibling) { target.nextElementSibling.remove(); } target.parentElement.insertAdjacentHTML('beforeend', html); } const initProvinceList = async () => { const searchParams = new URLSearchParams(location.search); const sido = searchParams.get('province') || ""; const provinceList = await getProvinceList(); const target = document.querySelector( 'script[type="text/x-handlebars-template"]#province-template' ); const source = target.innerHTML; const template = Handlebars.compile(source); const html = template({ provinces: provinceList, selectedValue: sido }); const provinceElem = document.querySelector('#province'); target.parentElement.innerHTML = html; provinceElem.value = sido; } const initFilter = () => { const searchParams = new URLSearchParams(location.search); const name = searchParams.get('name') || ""; const applicationOpen = searchParams.get('applicationOpen') || undefined; const provinceSelect = document.querySelector('#province'); provinceSelect.addEventListener('change', ACTION_MAPS['SEARCH']); const districtSelect = document.querySelector('#district'); districtSelect.addEventListener('change', ACTION_MAPS['SEARCH']); const academyInput = document.querySelector('#academy'); if(academyInput) { academyInput.addEventListener('keyup', (e) => { if(e.key === 'Enter') ACTION_MAPS.SEARCH(e); }); if(name) { academyInput.value = name; } } const openCheckbox = document.querySelector('#open'); if (openCheckbox) { openCheckbox.addEventListener('change', ACTION_MAPS.SEARCH); if(applicationOpen) { openCheckbox.checked = applicationOpen === 'true'; } } } const ACTION_MAPS = { APPLY_ONLINE: (e) => { const href = e.target.dataset.href; if(!href) return; window.open(href, '_blank'); }, MOVE_TO_HOMEPAGE: (e) => { const href = e.target.dataset.href; if(!href) return; window.open(href, '_blank'); }, TOGGLE_ACCORDION: (e) => { const row = e.target.closest('.accordion-row'); const panel = row?.nextElementSibling; const mapContainer = document.querySelector('#academy-map-container'); if (!row || !panel) return; const isOpen = panel.classList.contains('is-open'); document .querySelectorAll('.accordion-panel') .forEach((p) => p.classList.remove('is-open')); document .querySelectorAll('.accordion-row') .forEach((r) => r.classList.remove('is-open')); if (!isOpen) { panel.classList.add('is-open'); row.classList.add('is-open'); const mapBox = panel.querySelector('.map-box'); if(!mapBox || !map) return; mapBox.appendChild(mapContainer); mapContainer.style.display = 'block'; const position = new kakao.maps.LatLng( mapBox.dataset.lat, mapBox.dataset.lng ); requestAnimationFrame(() => { requestAnimationFrame(() => { map.relayout(); if(isInKorea(mapBox.dataset.lat, mapBox.dataset.lng)) { map.setCenter(position); marker.setMap(map); marker.setPosition(position); } else { map.setCenter(new kakao.maps.LatLng(MAP_DEFAULT_CENTER.lat, MAP_DEFAULT_CENTER.lng)); marker.setMap(null); } }); }); } else { if(!mapContainer) return; document.body.insertAdjacentElement('beforeend', mapContainer); mapContainer.style.display = 'none'; } }, CLOSE_ACCORDION: (e) => { const mapContainer = document.querySelector('#academy-map-container'); const panel = e.target.closest('.accordion-panel'); const row = panel?.previousElementSibling; if (!panel || !row || !mapContainer) return; panel.classList.remove('is-open'); row.classList.remove('is-open'); document.body.insertAdjacentElement('beforeend', mapContainer); mapContainer.style.display = 'none'; }, SEARCH: () => { const newSearchParams = new URLSearchParams(location.search); const province = document.querySelector('#province'); const district = document.querySelector('#district'); const keyword = document.querySelector('#academy'); const isProvinceChanged = newSearchParams.get('province') !== province?.value; const openCheckbox = document.querySelector('#open'); if(province?.value) { newSearchParams.set('province', province?.value); } else { newSearchParams.delete('province'); } if(isProvinceChanged) { newSearchParams.delete('district'); district.value = ''; } if(district?.value && district.value !== '전체') { newSearchParams.set('district', district?.value); } else { newSearchParams.delete('district'); } if(keyword?.value) { newSearchParams.set('name', keyword?.value); } else { newSearchParams.delete('name'); } if(openCheckbox?.checked) { newSearchParams.set('applicationOpen', true); } else { newSearchParams.delete('applicationOpen'); } history.replaceState( {}, '', newSearchParams.size ? `${location.pathname}?${newSearchParams.toString()}` : `${location.pathname}` ); fetchOnsiteExamAcademyList(); if(isProvinceChanged) { fetchDistrictList(); } }, MOVE_TO_PRODUCT: (e) => { window.open(`/pages/mock-test/premium/premium-detail.html?event=${DSDOENV.EventConfig.EVENT_NO_THE_PREMIUM}`); }, SHOW_MORE_ACADEMY: (e) => { const button = e.target; if (!button) return; const target = document.querySelector( '#academy-template' ); const template = Handlebars.compile(target.innerHTML); renderAcademies(target, template); }, SHOW_MORE_DEALER: (e) => { const button = e.target; if (!button) return; const target = document.querySelector( '#dealer-template' ); const template = Handlebars.compile(target.innerHTML); renderDealers(target, template); } } // dom control document.addEventListener('DOMContentLoaded', () => { document.addEventListener('click', (e) => { const target = e.target.closest('[data-action]'); if (!target) return; const action = target.dataset.action; const handler = ACTION_MAPS[action]; if (!handler) return; handler(e); }); initProvinceList(); initDealerList(); initFilter(); initMap(); fetchDistrictList(); fetchOnsiteExamAcademyList(); }); })();