diff --git a/src/main/resources/static/css/web/makeReservation.css b/src/main/resources/static/css/web/makeReservation.css new file mode 100644 index 0000000..b2ceb7b --- /dev/null +++ b/src/main/resources/static/css/web/makeReservation.css @@ -0,0 +1,470 @@ +* { + box-sizing: border-box; +} + +body { + font-family: 'Noto Sans KR', sans-serif; + margin: 0; + background: #fafafa; + color: #222; + height: 100vh; + display: flex; + flex-direction: column; +} + +/* Main container */ +.reservation-container { + display: flex; + max-width: 1280px !important; + margin: 0 auto; + gap: 20px; + height: 100%; + flex: 1; + padding: 20px 0; + margin-top: 65px; +} + +.box { + background: #fff; + border-radius: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + padding: 24px 16px; + flex: 1 1 0; + min-width: 260px; + display: flex; + flex-direction: column; + height: fit-content; + min-height: 600px; +} + +.step-title { + color: #b23c3c; + font-weight: 700; + font-size: 1.1em; + margin-bottom: 12px; + letter-spacing: 0.02em; + transition: color 0.3s ease; +} + +.step-title.completed { + color: #008000 !important; +} + +/* Service section */ +.service-list { + flex: 1; + margin-bottom: 24px; +} + +.service-item { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 1.1em; + margin-bottom: 10px; +} + +.service-item .del { + cursor: pointer; + color: #b23c3c; + margin-left: 8px; + font-size: 1.2em; + transition: color 0.3s ease, opacity 0.3s ease; +} + +.service-item .del:hover { + color: #ff0000; +} + +.service-item .del.disabled { + color: #ccc; + cursor: not-allowed; + opacity: 0.5; +} + +.service-item .del.disabled:hover { + color: #ccc; +} + +.service-item .price { + font-weight: bold; + color: #b23c3c; +} + +/* Service count indicator */ +.service-count-info { + font-size: 0.85em; + color: #888; + margin-bottom: 8px; + text-align: center; + padding: 4px 8px; + background: #f8f9fa; + border-radius: 4px; +} + +.service-count-info.single { + color: #ff8c00; + background: #fff3e0; + border: 1px solid #ffcc80; +} + +.total { + border-top: 1px solid #eee; + margin-top: auto; + padding-top: 16px; + display: flex; + justify-content: space-between; + font-size: 1.1em; + font-weight: bold; +} + +.total .price { + color: #b23c3c; +} + +.total small { + font-weight: normal; + color: #888; + font-size: 0.9em; + margin-right: 8px; +} + +/* Calendar section */ +.calendar-box { + margin-bottom: 20px; +} + +.calendar-header { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.calendar-header button { + background: none; + border: none; + font-size: 1.2em; + cursor: pointer; + color: #b23c3c; + padding: 0 6px; +} + +.calendar-table { + width: 100%; + border-collapse: collapse; + text-align: center; + margin-bottom: 8px; +} + +.calendar-table th, +.calendar-table td { + width: 2em; + height: 2em; + padding: 2px; + font-size: 1em; + border-radius: 50%; + cursor: pointer; + transition: background 0.15s; +} + +.calendar-table th { + color: #b23c3c; + font-weight: 500; + background: none; +} + +.calendar-table td.selected { + background: #b23c3c; + color: #fff; +} + +.calendar-table td.today { + border: 1.5px solid #b23c3c; +} + +.calendar-table td:not(.selected):hover { + background: #f5eaea; +} + +.calendar-table td.disabled { + color: #ccc; + pointer-events: none; + background: none; +} + +/* Time slots */ +.time-slots { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 12px; +} + +.time-btn { + flex: 1 0 30%; + min-width: 80px; + padding: 8px 0; + border: 1px solid #b23c3c; + border-radius: 20px; + background: #fff; + color: #b23c3c; + font-size: 1em; + cursor: pointer; + transition: background 0.15s, color 0.15s; +} + +.time-btn.selected, +.time-btn:active { + background: #b23c3c; + color: #fff; +} + +.time-btn:disabled { + color: #ccc; + border-color: #eee; + background: #f5f5f5; + cursor: not-allowed; +} + +.person-count { + margin-top: 12px; + font-size: 0.98em; + color: #888; +} + +/* Form section */ +.form-group { + margin-bottom: 16px; +} + +.form-group label { + display: block; + margin-bottom: 4px; + font-weight: 500; +} + +.form-group input, +.form-group textarea { + width: 100%; + padding: 8px 10px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 1em; + resize: none; + transition: border-color 0.3s ease; +} + +.form-group textarea { + min-height: 60px; +} + +.checkbox-group { + display: flex; + align-items: center; + margin-bottom: 18px; + font-size: 0.98em; +} + +.checkbox-group input[type="checkbox"] { + margin-right: 8px; + accent-color: #b23c3c; +} + +.submit-btn { + width: 100%; + padding: 14px 0; + background: #ddd; + color: #888; + border: none; + border-radius: 8px; + font-size: 1.1em; + font-weight: bold; + cursor: not-allowed; + transition: all 0.3s ease; +} + +.submit-btn.step-progress { + background: linear-gradient(45deg, #ddd, #bbb); + color: #666; + font-size: 0.95em; +} + +.submit-btn.ready { + background: #b23c3c; + color: #fff; + cursor: pointer; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.02); } + 100% { transform: scale(1); } +} + +/* Phone message styles (common.js PhoneValidator용) */ +.phone-message { + font-size: 12px; + margin-top: 5px; + padding: 4px 8px; + border-radius: 4px; + font-weight: 500; + transition: all 0.3s ease; +} + +.phone-message.error { + color: #ff0000; + background-color: #ffebee; + border: 1px solid #ffcdd2; +} + +.phone-message.warning { + color: #ff8c00; + background-color: #fff3e0; + border: 1px solid #ffcc80; +} + +.phone-message.success { + color: #008000; + background-color: #f1f8e9; + border: 1px solid #c8e6c9; +} + +.phone-message.info { + color: #2196f3; + background-color: #e3f2fd; + border: 1px solid #90caf9; +} + +/* Birth date message styles */ +.birth-date-message { + font-size: 12px; + margin-top: 5px; + padding: 4px 8px; + border-radius: 4px; + font-weight: 500; + transition: all 0.3s ease; +} + +.birth-date-message.error { + color: #ff0000; + background-color: #ffebee; + border: 1px solid #ffcdd2; +} + +.birth-date-message.warning { + color: #ff8c00; + background-color: #fff3e0; + border: 1px solid #ffcc80; +} + +.birth-date-message.success { + color: #008000; + background-color: #f1f8e9; + border: 1px solid #c8e6c9; +} + +.birth-date-message.info { + color: #2196f3; + background-color: #e3f2fd; + border: 1px solid #90caf9; +} + +/* Disabled calendar cell - 강화된 스타일 */ +.calendar-table td.disabled { + color: #ccc; + cursor: not-allowed; + pointer-events: none; + background: none !important; +} + +.calendar-table td.disabled:hover { + background: none !important; + cursor: not-allowed !important; +} + +/* Responsive Design */ +@media (max-width: 1199.98px) { + .reservation-container { + max-width: 960px !important; + gap: 16px; + padding: 20px 15px; + } + + .box { + padding: 20px 14px; + } +} + +@media (max-width: 991.98px) { + .reservation-container { + max-width: 720px !important; + flex-direction: column; + gap: 24px; + height: auto; + padding: 20px 15px; + } + + .box { + min-height: unset; + min-width: unset; + } +} + +@media (max-width: 767.98px) { + .reservation-container { + max-width: 540px !important; + padding: 15px; + gap: 20px; + } + + .box { + padding: 16px 12px; + } + + .calendar-table th, + .calendar-table td { + width: 1.8em; + height: 1.8em; + } + + .time-btn { + min-width: 70px; + font-size: 0.95em; + } +} + +@media (max-width: 575.98px) { + .reservation-container { + max-width: 100% !important; + padding: 10px; + gap: 16px; + } + + .box { + padding: 16px 8px; + } + + .calendar-table th, + .calendar-table td { + width: 1.5em; + height: 1.5em; + font-size: 0.9em; + } + + .time-btn { + min-width: 60px; + font-size: 0.9em; + padding: 6px 0; + } + + .step-title { + font-size: 1em; + } + + .service-item { + font-size: 1em; + } +} diff --git a/src/main/resources/static/js/makeReservation.js b/src/main/resources/static/js/makeReservation.js new file mode 100644 index 0000000..19ae860 --- /dev/null +++ b/src/main/resources/static/js/makeReservation.js @@ -0,0 +1,598 @@ +// 휴일(완전 휴무) 설정 +const disabledSpecificDates = [ + '2025-12-25', // 크리스마스 + '2026-01-01' // 신정 +]; + +// 단축 진료일 (15:30까지, 점심시간 없음) +const shortWorkingDates = [ + '2025-12-24', // 크리스마스 이브 + '2025-12-31' // 연말 +]; + +// 점심시간 (14:00 ~ 15:00) +const lunchTimeStart = 1400; // 14:00 +const lunchTimeEnd = 1500; // 15:00 + +// 날짜가 휴무일인지 확인 +function isDateDisabled(date) { + const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; + return disabledSpecificDates.includes(dateStr); +} + +// 단축 근무일인지 확인 +function isShortWorkingDate(date) { + const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; + return shortWorkingDates.includes(dateStr); +} + +// 점심시간인지 확인 (월화수목금만 해당) +function isLunchTime(timeStr) { + const timeNum = parseInt(timeStr.replace(':', '')); // "14:30" -> 1430 + return timeNum >= lunchTimeStart && timeNum < lunchTimeEnd; +} + +// 생년월일 검증 클래스 +class BirthDateValidator { + constructor(inputId, options = {}) { + this.inputElement = document.getElementById(inputId); + this.messageElement = null; + this.options = { + showMessage: true, + realTimeValidation: true, + minAge: 0, + maxAge: 150, + format: 'YYYYMMDD', + allowFuture: false, + onValidationChange: null, + ...options + }; + + if (this.inputElement && this.options.showMessage) { + this.createMessageElement(); + } + + if (this.inputElement && this.options.realTimeValidation) { + this.bindEvents(); + } + } + + createMessageElement() { + this.messageElement = document.createElement('div'); + this.messageElement.className = 'birth-date-message'; + this.messageElement.style.display = 'none'; + this.inputElement.parentNode.insertBefore(this.messageElement, this.inputElement.nextSibling); + } + + bindEvents() { + this.inputElement.addEventListener('input', () => { + this.inputElement.value = this.inputElement.value.replace(/[^0-9]/g, ''); + this.validateAndShowMessage(); + }); + + this.inputElement.addEventListener('blur', () => { + this.validateAndShowMessage(); + }); + + this.inputElement.addEventListener('keydown', (e) => { + if ([8, 9, 46, 37, 38, 39, 40].includes(e.keyCode)) return; + if ((e.keyCode < 48 || e.keyCode > 57) && (e.keyCode < 96 || e.keyCode > 105)) { + e.preventDefault(); + } + }); + } + + validateBirthDate(dateStr) { + if (!dateStr) return { valid: false, message: '생년월일을 입력해주세요.' }; + + let cleanDate = dateStr.replace(/[^0-9]/g, ''); + if (cleanDate.length !== 8) { + return { valid: false, message: '생년월일은 8자리 숫자로 입력해주세요. (예: 19900115)' }; + } + + const year = parseInt(cleanDate.slice(0, 4)); + const month = parseInt(cleanDate.slice(4, 6)); + const day = parseInt(cleanDate.slice(6, 8)); + + const currentYear = new Date().getFullYear(); + const minYear = currentYear - this.options.maxAge; + const maxYear = currentYear - this.options.minAge; + + if (year < minYear || year > maxYear) { + return { valid: false, message: `출생연도는 ${minYear}년부터 ${maxYear}년 사이여야 합니다.` }; + } + + if (month < 1 || month > 12) return { valid: false, message: '월은 01부터 12까지 입력 가능합니다.' }; + if (day < 1 || day > 31) return { valid: false, message: '일은 01부터 31까지 입력 가능합니다.' }; + + const date = new Date(year, month - 1, day); + const isValidDate = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day; + + if (!isValidDate) return { valid: false, message: '존재하지 않는 날짜입니다.' }; + if (!this.options.allowFuture && date > new Date()) return { valid: false, message: '미래 날짜는 입력할 수 없습니다.' }; + + return { valid: true, message: '올바른 생년월일입니다.' }; + } + + validateAndShowMessage() { + const result = this.validateBirthDate(this.inputElement.value); + if (this.messageElement) { + this.messageElement.textContent = result.message; + this.messageElement.className = `birth-date-message ${result.valid ? 'success' : 'error'}`; + this.messageElement.style.display = 'block'; + } + if (typeof this.options.onValidationChange === 'function') { + this.options.onValidationChange(result, this.inputElement.value); + } + return result.valid; + } + + isValid() { + return this.validateBirthDate(this.inputElement.value).valid; + } +} + +// 전역 변수 +let birthDateValidator; +let selectedTreatments = []; +let selectedDate = null; +let selectedTime = null; +let selectedYear, selectedMonth; + +// DOM 요소 +const calendarTitle = document.getElementById('calendar-title'); +const calendarTable = document.getElementById('calendar-table'); +const timeSlots = document.getElementById('time-slots'); +const personCount = document.getElementById('person-count'); +const form = document.getElementById('reserve-form'); +const agree = document.getElementById('agree'); +const submitBtn = document.getElementById('submit-btn'); +const step02Title = document.getElementById('step02-title'); +const step03Title = document.getElementById('step03-title'); + +// 진료시간 설정 (점심시간 제외) +const times_mon_wed_fri = [ + "10:00","10:30","11:00","11:30","12:00","12:30","13:00","13:30", + "15:00","15:30","16:00","16:30","17:00","17:30","18:00","18:30" +]; + +const times_tue_thu = [ + "10:00","10:30","11:00","11:30","12:00","12:30","13:00","13:30", + "15:00","15:30","16:00","16:30","17:00","17:30","18:00","18:30","19:00","19:30" +]; + +const times_sat_short = [ + "10:00","10:30","11:00","11:30","12:00","12:30","13:00","13:30", + "14:00","14:30","15:00","15:30" +]; + +// 시술 관리 함수들 +function removeService(el) { + const serviceItems = document.querySelectorAll('.service-item'); + if (serviceItems.length <= 1) { + alert('최소 1개의 시술은 선택되어 있어야 합니다.'); + return false; + } + + const serviceName = el.closest('.service-item').querySelector('span:first-child').textContent; + if (!confirm(`'${serviceName}' 시술을 삭제하시겠습니까?`)) return false; + + el.closest('.service-item').remove(); + updateTotalPrice(); + updateServiceCount(); + return true; +} + +function updateTotalPrice() { + const serviceItems = document.querySelectorAll('.service-item'); + let totalPrice = 0; + serviceItems.forEach(item => { + const priceText = item.querySelector('.price').textContent; + totalPrice += parseInt(priceText.replace(/[^0-9]/g, '')) || 0; + }); + document.getElementById('total-price').textContent = totalPrice.toLocaleString() + '원'; +} + +function updateServiceCount() { + const serviceItems = document.querySelectorAll('.service-item'); + const count = serviceItems.length; + const info = document.getElementById('service-count-info'); + info.textContent = `선택된 시술: ${count}개${count === 1 ? ' (최소 필수)' : ''}`; + info.className = count === 1 ? 'service-count-info single' : 'service-count-info'; + + serviceItems.forEach(item => { + const delBtn = item.querySelector('.del'); + if (count <= 1) { + delBtn.className = 'del disabled'; + delBtn.title = '최소 1개의 시술은 필요합니다'; + } else { + delBtn.className = 'del'; + delBtn.title = '삭제'; + } + }); +} + +// 캘린더 렌더링 (일요일 + 공휴일만 휴무) +function renderCalendar(year, month) { + selectedYear = year; + selectedMonth = month; + calendarTitle.textContent = `${year}.${String(month + 1).padStart(2, '0')}`; + + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const todayDate = new Date(); + todayDate.setHours(0, 0, 0, 0); + + let html = ''; + ['일','월','화','수','목','금','토'].forEach(d => html += `${d}`); + html += ''; + + for(let i = 0; i < firstDay.getDay(); i++) html += ''; + + for(let d = 1; d <= lastDay.getDate(); d++) { + const dateObj = new Date(year, month, d); + let classes = []; + + const isPastDate = dateObj < todayDate; + const isSunday = dateObj.getDay() === 0; + const isHoliday = isDateDisabled(dateObj); + + if(dateObj.toDateString() === new Date().toDateString()) classes.push('today'); + if(selectedDate && dateObj.toDateString() === selectedDate.toDateString()) classes.push('selected'); + + if(isPastDate || isSunday || isHoliday) classes.push('disabled'); + + const clickHandler = (isPastDate || isSunday || isHoliday) ? + '' : `onclick="selectDate(${year},${month},${d})"`; + html += `${d}`; + + if((firstDay.getDay() + d) % 7 === 0 && d !== lastDay.getDate()) html += ''; + } + + for (let i = lastDay.getDay(); i < 6; i++) { + html += ''; + } + html += ''; + calendarTable.innerHTML = html; + checkForm(); +} + +function selectDate(y, m, d) { + const tempDate = new Date(y, m, d); + const todayDate = new Date(); + todayDate.setHours(0, 0, 0, 0); + + if (tempDate < todayDate) { + alert('과거 날짜는 선택할 수 없습니다.'); + return; + } + + if (tempDate.getDay() === 0) { + alert('일요일은 휴무일입니다.'); + return; + } + + if (isDateDisabled(tempDate)) { + alert('해당 날짜는 휴무일입니다.'); + return; + } + + selectedDate = tempDate; + renderCalendar(y, m); + renderTimeSlots(); + checkForm(); +} + +// 월 이동 +document.getElementById('prev-month').onclick = function() { + selectedMonth === 0 ? (selectedYear--, selectedMonth = 11) : selectedMonth--; + renderCalendar(selectedYear, selectedMonth); +}; + +document.getElementById('next-month').onclick = function() { + selectedMonth === 11 ? (selectedYear++, selectedMonth = 0) : selectedMonth++; + renderCalendar(selectedYear, selectedMonth); +}; + +// 시간 슬롯 렌더링 (점심시간 제외 + 특수일 처리) +function renderTimeSlots() { + if (!selectedDate) { + timeSlots.innerHTML = ''; + return; + } + + const dayOfWeek = selectedDate.getDay(); + if (dayOfWeek === 0 || isDateDisabled(selectedDate)) { + timeSlots.innerHTML = ''; + return; + } + + let slotArr; + if (isShortWorkingDate(selectedDate)) { + slotArr = times_sat_short; // 12/24, 12/31: 토요일과 동일 + } else if ([1, 3, 5].includes(dayOfWeek)) { // 월수금 + slotArr = times_mon_wed_fri; + } else if ([2, 4].includes(dayOfWeek)) { // 화목 + slotArr = times_tue_thu; + } else if (dayOfWeek === 6) { // 토 + slotArr = times_sat_short; + } else { + timeSlots.innerHTML = ''; + return; + } + + const now = new Date(); + const todayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const isToday = selectedDate.toDateString() === todayDate.toDateString(); + + let html = ''; + slotArr.forEach(t => { + let isDisabled = false; + if (isToday) { + const currentTime = now.getHours() * 100 + now.getMinutes(); + const [hour, minute] = t.split(':').map(Number); + if (hour * 100 + minute <= currentTime) isDisabled = true; + } + + const selectedClass = selectedTime === t && !isDisabled ? ' selected' : ''; + const disabledAttr = isDisabled ? 'disabled' : ''; + html += ``; + }); + + timeSlots.innerHTML = html; +} + +function selectTimeAndCall(t, el) { + const now = new Date(); + const todayDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const isToday = selectedDate.toDateString() === todayDate.toDateString(); + + if (isToday) { + const currentTime = now.getHours() * 100 + now.getMinutes(); + const [hour, minute] = t.split(':').map(Number); + if (hour * 100 + minute <= currentTime) { + alert('지난 시간은 선택할 수 없습니다.'); + return; + } + } + + selectedTime = t; + renderTimeSlots(); + if (el) { + document.querySelectorAll('.time-btn').forEach(btn => btn.classList.remove('active')); + el.classList.add('active'); + } + onClickTime(getSelectedDateStr(), t); + checkForm(); +} + +function getSelectedDateStr() { + if (!selectedDate) return ''; + return selectedDate.getFullYear() + + String(selectedDate.getMonth() + 1).padStart(2, '0') + + String(selectedDate.getDate()).padStart(2, '0'); +} + +// 폼 검증 +function checkForm() { + const name = document.getElementById('customer-name').value.trim(); + const phone = document.getElementById('customer-phone').value.trim(); + const birthDate = document.getElementById('birthDate').value.trim(); + + const phoneValid = phone.match(/^01[0-9]{8,9}$/) || + (typeof PhoneValidator !== 'undefined' && PhoneValidator.isValid?.('customer-phone')); + const birthDateValid = birthDate.match(/^\d{8}$/) || + (birthDateValidator?.isValid?.()); + + const conditions = { + date: !!selectedDate, + time: !!selectedTime, + name: !!name, + phone: !!phoneValid, + birthDate: !!birthDateValid, + agree: agree.checked + }; + + const valid = Object.values(conditions).every(Boolean); + submitBtn.disabled = !valid; + + updateStepStatus(conditions); + updateButtonText(conditions); + + submitBtn.className = valid ? 'submit-btn ready' : + selectedDate ? 'submit-btn step-progress' : 'submit-btn'; +} + +function updateStepStatus(conditions) { + step02Title.textContent = (conditions.date && conditions.time) ? 'STEP 02. 예약 시간 선택 ✓' : 'STEP 02. 예약 시간 선택'; + step02Title.className = (conditions.date && conditions.time) ? 'step-title completed' : 'step-title'; + + step03Title.textContent = (conditions.name && conditions.phone && conditions.birthDate && conditions.agree) ? + 'STEP 03. 고객정보 ✓' : 'STEP 03. 고객정보'; + step03Title.className = (conditions.name && conditions.phone && conditions.birthDate && conditions.agree) ? + 'step-title completed' : 'step-title'; +} + +function updateButtonText(conditions) { + if (!conditions.date) return submitBtn.textContent = '📅 예약 날짜를 선택해주세요'; + if (!conditions.time) return submitBtn.textContent = '⏰ 예약 시간을 선택해주세요'; + if (!conditions.name) return submitBtn.textContent = '👤 고객명을 입력해주세요'; + if (!conditions.birthDate) return submitBtn.textContent = '📅 생년월일을 올바르게 입력해주세요'; + if (!conditions.phone) return submitBtn.textContent = '📱 연락처를 올바르게 입력해주세요'; + if (!conditions.agree) return submitBtn.textContent = '✅ 개인정보 동의를 체크해주세요'; + submitBtn.textContent = '🎉 시술 예약하기'; +} + +// 초기화 +const today = new Date(); +selectedYear = today.getFullYear(); +selectedMonth = today.getMonth(); + +form.addEventListener('input', (e) => { + if (e.target.id !== 'customer-phone' && e.target.id !== 'birthDate') setTimeout(checkForm, 10); +}); +agree.addEventListener('change', checkForm); + +form.onsubmit = function(e) { + e.preventDefault(); + if (!selectedDate || !selectedTime) { + alert('예약 날짜와 시간을 선택해 주세요.'); + return; + } + if (!birthDateValidator?.isValid()) { + alert('올바른 생년월일을 입력해 주세요.'); + return; + } + fn_reservation(); +}; + +// AJAX 함수들 (기존 그대로) +function fn_reservation() { + let formData = new FormData(); + if (selectedDate) { + formData.append('SELECTED_DATE', `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}`); + } + if (selectedTime) formData.append('TIME', selectedTime); + formData.append('CATEGORY_DIV_CD', typeof category_div_cd !== 'undefined' ? category_div_cd : ''); + formData.append('CATEGORY_NO', typeof category_no !== 'undefined' ? category_no : ''); + formData.append('POST_NO', typeof post_no !== 'undefined' ? post_no : ''); + formData.append('NAME', document.getElementById('customer-name').value); + formData.append('BIRTH_DATE', document.getElementById('birthDate').value); + formData.append('PHONE_NUMBER', document.getElementById('customer-phone').value); + formData.append('ETC', document.getElementById('customer-req').value); + formData.append('TREATMENT_INFOS', JSON.stringify(selectedTreatments)); + + $.ajax({ + url: encodeURI('/webservice/insertReservation.do'), + data: formData, + dataType: 'json', + processData: false, + contentType: false, + type: 'POST', + async: true, + success: function(data) { + if (data.msgCode == '0') { + alert('예약이 완료되었습니다.'); + location.href = "/webevent/selectListWebEventIntro.do"; + } else { + modalEvent.danger("조회 오류", data.msgDesc); + } + }, + error: function() { + modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오."); + }, + beforeSend: function() { $(".loading-image-layer").show(); }, + complete: function() { $(".loading-image-layer").hide(); } + }); +} + +function fn_SelectReservation(category_div_cd, category_no, post_no, procedure_id) { + let formData = new FormData(); + formData.append('CATEGORY_DIV_CD', category_div_cd); + formData.append('CATEGORY_NO', category_no); + formData.append('POST_NO', post_no); + formData.append('PROCEDURE_ID', procedure_id); + + $.ajax({ + url: encodeURI('/webservice/selectReservation.do'), + data: formData, + dataType: 'json', + processData: false, + contentType: false, + type: 'POST', + async: true, + success: function(data) { + if (data.msgCode == '0') { + const serviceList = document.getElementById('service-list'); + serviceList.innerHTML = ''; + let totalprice = 0; + selectedTreatments = []; + + if (data.reservation?.length > 0) { + data.reservation.forEach(item => { + let price = item.DISCOUNT_PRICE ?? item.PRICE ?? 0; + totalprice += Number(price); + + selectedTreatments.push({ + MU_TREATMENT_ID: item.MU_TREATMENT_ID, + TREATMENT_NAME: item.TREATMENT_NAME, + TREATMENT_PROCEDURE_NAME: item.TREATMENT_PROCEDURE_NAME, + MU_TREATMENT_PROCEDURE_ID: item.MU_TREATMENT_PROCEDURE_ID + }); + + const div = document.createElement('div'); + div.className = 'service-item'; + div.innerHTML = ` + ${item.TREATMENT_PROCEDURE_NAME} + + ${item.DISCOUNT_PRICE != null ? `${(item.PRICE || 0).toLocaleString()}원` : ''} + ${Number(price).toLocaleString()}원 + × + + `; + serviceList.appendChild(div); + }); + } + document.getElementById('total-price').textContent = totalprice.toLocaleString() + '원'; + updateServiceCount(); + } else { + modalEvent.danger("조회 오류", data.msgDesc); + } + }, + error: function() { + modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오."); + }, + beforeSend: function() { $(".loading-image-layer").show(); }, + complete: function() { $(".loading-image-layer").hide(); } + }); +} + +function onClickTime(selectedDateStr, time) { + let formData = new FormData(); + formData.append('SELECTED_DATE', selectedDateStr); + formData.append('TIME', time); + + $.ajax({ + url: encodeURI('/webservice/selectReservationCnt.do'), + data: formData, + dataType: 'json', + processData: false, + contentType: false, + type: 'POST', + async: true, + success: function(data) { + personCount.textContent = data.msgCode == '0' && data.rows?.RES_CNT !== undefined ? + data.rows.RES_CNT : '-'; + }, + error: function() { + personCount.textContent = '-'; + } + }); +} + +// 문서 준비 완료 +$(document).ready(function() { + // Validator 초기화 + try { + birthDateValidator = new BirthDateValidator('birthDate', { + showMessage: true, + realTimeValidation: true, + onValidationChange: () => setTimeout(checkForm, 10) + }); + } catch (e) { + console.error('BirthDateValidator init error:', e); + } + + // 초기 캘린더 및 시술 로드 + const today = new Date(); + let initDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()); + while (initDate.getDay() === 0 || isDateDisabled(initDate)) { + initDate.setDate(initDate.getDate() + 1); + } + + renderCalendar(today.getFullYear(), today.getMonth()); + fn_SelectReservation(category_div_cd, category_no, post_no, procedure_id); + + setTimeout(checkForm, 500); +}); \ No newline at end of file diff --git a/src/main/resources/templates/web/service/makeReservation.html b/src/main/resources/templates/web/service/makeReservation.html index 3add670..5644e8d 100644 --- a/src/main/resources/templates/web/service/makeReservation.html +++ b/src/main/resources/templates/web/service/makeReservation.html @@ -4,457 +4,9 @@ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/web/layout/layout}"> - - + + + @@ -470,7 +22,7 @@
-
STEP 01. 이벤트 예약
+
STEP 01. 시술 예약
선택된 시술: 1개
@@ -547,874 +99,6 @@ - + diff --git a/src/main/resources/templates/web/webevent/makeReservation.html b/src/main/resources/templates/web/webevent/makeReservation.html index b97dd2a..09770a5 100644 --- a/src/main/resources/templates/web/webevent/makeReservation.html +++ b/src/main/resources/templates/web/webevent/makeReservation.html @@ -5,456 +5,9 @@ layout:decorate="~{/web/layout/layout}"> - + + + @@ -547,874 +100,6 @@ - +