727 lines
35 KiB
PHP
727 lines
35 KiB
PHP
<?php
|
||
/**
|
||
* 전문가 방문 예약 팝업 UI 및 데이터 처리
|
||
*/
|
||
|
||
// AJAX 요청 처리
|
||
if (isset($_POST['action'])) {
|
||
include_once('../_common_con.php');
|
||
include_once('../lib/notification_helper.php');
|
||
header('Content-Type: application/json');
|
||
|
||
$action = $_POST['action'] ?? '';
|
||
$response = ['success' => false, 'message' => '알 수 없는 요청입니다.'];
|
||
|
||
// 월별 예약 가능일 조회
|
||
if ($action === 'get_expert_visit_month_availability') {
|
||
$year = (int) ($_POST['year'] ?? 0);
|
||
$month = (int) ($_POST['month'] ?? 0);
|
||
$expert_id = $_POST['expert_id'] ?? ''; // 전문가 ID 추가
|
||
|
||
if ($year && $month) {
|
||
$start_date = date('Y-m-d', mktime(0, 0, 0, $month, 1, $year));
|
||
$end_date = date('Y-m-t', strtotime($start_date));
|
||
|
||
$max_advance_days = get_order_config('expert_visit_max_advance_days', 30);
|
||
$max_date = date('Y-m-d', strtotime("+" . $max_advance_days . " days"));
|
||
|
||
// 날짜별로 루프를 돌며 가용성을 체크합니다.
|
||
// 쿼리 하나로 처리하기 복잡하므로, 기간 내의 모든 스케줄을 가져와서 PHP에서 병합합니다.
|
||
|
||
// 1. 기간 내의 모든 '지정일' 스케줄 가져오기
|
||
$specific_schedules = [];
|
||
$sql_specific = "SELECT * FROM expert_visit_schedules
|
||
WHERE specific_date BETWEEN '{$start_date}' AND '{$end_date}'
|
||
AND (expert_id = '{$expert_id}' OR expert_id IS NULL)
|
||
ORDER BY expert_id DESC"; // 전문가 설정 우선
|
||
$res_specific = sql_query($sql_specific);
|
||
while ($row = sql_fetch_array($res_specific)) {
|
||
$date = $row['specific_date'];
|
||
if (!isset($specific_schedules[$date])) { // 이미 전문가 설정이 있으면 공통 설정은 무시
|
||
$specific_schedules[$date] = $row;
|
||
}
|
||
}
|
||
|
||
// 2. '요일별' 스케줄 가져오기
|
||
$weekly_schedules = [];
|
||
$sql_weekly = "SELECT * FROM expert_visit_schedules
|
||
WHERE day_of_week IS NOT NULL
|
||
AND (expert_id = '{$expert_id}' OR expert_id IS NULL)
|
||
ORDER BY expert_id DESC"; // 전문가 설정 우선
|
||
$res_weekly = sql_query($sql_weekly);
|
||
while ($row = sql_fetch_array($res_weekly)) {
|
||
$dow = $row['day_of_week'];
|
||
if (!isset($weekly_schedules[$dow])) {
|
||
$weekly_schedules[$dow] = $row;
|
||
}
|
||
}
|
||
|
||
$availability = [];
|
||
$current = strtotime($start_date);
|
||
$end = strtotime($end_date);
|
||
|
||
while ($current <= $end) {
|
||
$date_str = date('Y-m-d', $current);
|
||
$day_num = date('j', $current);
|
||
$dow = date('N', $current); // 1(월) ~ 7(일)
|
||
|
||
// 우선순위: 지정일 > 요일별
|
||
$schedule = $specific_schedules[$date_str] ?? ($weekly_schedules[$dow] ?? null);
|
||
|
||
$is_bookable = false;
|
||
$reason = '';
|
||
|
||
if (!$schedule) {
|
||
$reason = 'no_schedule'; // 스케줄 없음
|
||
} elseif ($schedule['is_available'] == 0) {
|
||
$reason = 'holiday'; // 휴무
|
||
} elseif ($date_str < date('Y-m-d')) {
|
||
$reason = 'past_date'; // 지난 날짜
|
||
} elseif ($date_str > $max_date) {
|
||
$reason = 'too_far'; // 예약 가능 기간 초과
|
||
} else {
|
||
// 예약 꽉 찼는지 확인
|
||
// 해당 날짜, 해당 전문가(또는 전체)의 예약 건수 확인
|
||
// 시간대별로 체크해야 정확하지만, 여기서는 '하루 전체 마감' 여부를 대략적으로 판단하거나
|
||
// 일단 '가능'으로 표시하고 시간 선택에서 막을 수 있습니다.
|
||
// 정확도를 위해 해당 날짜의 총 슬롯 수와 예약 수를 비교합니다.
|
||
|
||
$start_time = strtotime($date_str . ' ' . $schedule['start_time']);
|
||
$end_time = strtotime($date_str . ' ' . $schedule['end_time']);
|
||
$slot_duration = $schedule['time_slot'] * 60;
|
||
$total_slots = 0;
|
||
for ($t = $start_time; $t < $end_time; $t += $slot_duration) {
|
||
$total_slots++;
|
||
}
|
||
$max_capacity = $total_slots * $schedule['max_persons'];
|
||
|
||
// 예약된 건수 조회
|
||
$sql_reserved = "SELECT COUNT(*) as cnt FROM expert_visit_reservations
|
||
WHERE visit_date = '{$date_str}' AND status != 'cancelled'";
|
||
if ($expert_id) {
|
||
$sql_reserved .= " AND expert_id = '{$expert_id}'";
|
||
}
|
||
$row_reserved = sql_fetch($sql_reserved);
|
||
|
||
if ($row_reserved['cnt'] >= $max_capacity) {
|
||
$reason = 'full';
|
||
} else {
|
||
$is_bookable = true;
|
||
}
|
||
}
|
||
|
||
$availability[$day_num] = [
|
||
'available' => $is_bookable,
|
||
'reason' => $reason
|
||
];
|
||
|
||
$current = strtotime('+1 day', $current);
|
||
}
|
||
|
||
$response = ['success' => true, 'data' => $availability];
|
||
}
|
||
}
|
||
|
||
// 특정일의 예약 가능 시간 조회
|
||
if ($action === 'get_expert_visit_time_slots') {
|
||
$date = preg_replace('/[^0-9\-]/', '', $_POST['date'] ?? '');
|
||
$expert_id = $_POST['expert_id'] ?? '';
|
||
|
||
if ($date) {
|
||
$min_advance_hours = get_order_config('expert_visit_min_advance_hours', 24);
|
||
$min_datetime = date('Y-m-d H:i:s', strtotime("+" . $min_advance_hours . " hours"));
|
||
$dow = date('N', strtotime($date));
|
||
|
||
// 1. 해당 날짜의 스케줄 조회 (우선순위 적용)
|
||
$schedule = sql_fetch("SELECT * FROM expert_visit_schedules
|
||
WHERE (specific_date = '{$date}' OR (specific_date IS NULL AND day_of_week = '{$dow}'))
|
||
AND (expert_id = '{$expert_id}' OR expert_id IS NULL)
|
||
ORDER BY specific_date DESC, expert_id DESC LIMIT 1");
|
||
|
||
$slots = [];
|
||
if ($schedule && $schedule['is_available']) {
|
||
$start_time = strtotime($date . ' ' . $schedule['start_time']);
|
||
$end_time = strtotime($date . ' ' . $schedule['end_time']);
|
||
$slot_duration = $schedule['time_slot'] * 60;
|
||
$max_persons = $schedule['max_persons'];
|
||
|
||
for ($t = $start_time; $t < $end_time; $t += $slot_duration) {
|
||
$current_slot_time = date('H:i', $t);
|
||
$slot_datetime = $date . ' ' . $current_slot_time;
|
||
|
||
// 예약 건수 조회
|
||
$sql_reserved = "SELECT COUNT(*) as cnt FROM expert_visit_reservations
|
||
WHERE visit_date = '{$date}'
|
||
AND visit_time = '{$current_slot_time}:00'
|
||
AND status != 'cancelled'";
|
||
if ($expert_id) {
|
||
$sql_reserved .= " AND expert_id = '{$expert_id}'";
|
||
}
|
||
$reserved_count = (int)sql_fetch($sql_reserved)['cnt'];
|
||
|
||
$is_too_soon = ($slot_datetime < $min_datetime);
|
||
$is_full = ($reserved_count >= $max_persons);
|
||
|
||
$slots[] = [
|
||
'time' => $current_slot_time,
|
||
'available' => !$is_too_soon && !$is_full,
|
||
'reason' => $is_too_soon ? 'too_soon' : ($is_full ? 'full' : ''),
|
||
'reserved_count' => $reserved_count,
|
||
'max_persons' => $max_persons
|
||
];
|
||
}
|
||
}
|
||
$response = ['success' => true, 'data' => $slots];
|
||
}
|
||
}
|
||
|
||
echo json_encode($response);
|
||
exit;
|
||
}
|
||
|
||
if (!defined('_GNUBOARD_')) exit;
|
||
|
||
include_once(G5_ADMIN_PATH . '/order_manage/_common_con.php');
|
||
include_once(G5_ADMIN_PATH . '/order_manage/lib/notification_helper.php');
|
||
|
||
$current_year = date('Y');
|
||
$current_month_num = date('n');
|
||
|
||
$visit_fee = get_order_config('expert_visit_fee', 50000);
|
||
$account_info = get_order_config('expert_visit_account_info', '');
|
||
$max_advance_days = get_order_config('expert_visit_max_advance_days', 30);
|
||
|
||
$ajax_url = G5_ADMIN_URL . '/order_manage/components/expert_visit_popup.php';
|
||
$form_action_url = G5_ADMIN_URL . '/order_manage/components/expert_visit_submit.php';
|
||
$css_url = G5_ADMIN_URL . '/order_manage/components/expert_visit_popup.css';
|
||
|
||
$wr_id = isset($_GET['wr_id']) ? (int)$_GET['wr_id'] : 0;
|
||
|
||
// 💡 [추가] 로그인 회원 정보 가져오기
|
||
$customer_name = '';
|
||
$customer_phone = '';
|
||
$customer_email = '';
|
||
|
||
if (isset($member) && $member['mb_id']) {
|
||
$customer_name = $member['mb_name'];
|
||
$customer_phone = $member['mb_hp'];
|
||
$customer_email = $member['mb_email'];
|
||
}
|
||
?>
|
||
|
||
<link rel="stylesheet" href="<?php echo $css_url; ?>">
|
||
|
||
<div id="expert-visit-popup-overlay" class="expert-visit-modal-overlay">
|
||
<div id="expert-visit-popup" class="expert-visit-modal-content">
|
||
<div class="expert-visit-modal-header">
|
||
<h2>전문가 방문 예약</h2>
|
||
<button type="button" class="expert-visit-modal-close" aria-label="팝업 닫기">
|
||
<span>×</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="expert-visit-modal-body">
|
||
<div class="loading-overlay" style="display: none;">
|
||
<div class="loading-spinner"></div>
|
||
</div>
|
||
|
||
<div class="expert-visit-steps">
|
||
<div class="step active" data-step="1">
|
||
<span class="step-number">1</span>
|
||
<span class="step-text">날짜 선택</span>
|
||
</div>
|
||
<div class="step" data-step="2">
|
||
<span class="step-number">2</span>
|
||
<span class="step-text">시간 선택</span>
|
||
</div>
|
||
<div class="step" data-step="3">
|
||
<span class="step-number">3</span>
|
||
<span class="step-text">정보 입력</span>
|
||
</div>
|
||
</div>
|
||
|
||
<form id="expert-visit-form" method="post" action="<?php echo $form_action_url; ?>">
|
||
<div class="expert-visit-step-content" data-step="1">
|
||
<div class="step-description">
|
||
<h4>📅 방문 날짜를 선택해주세요</h4>
|
||
<p>최대 <?php echo $max_advance_days; ?>일 후까지 예약 가능합니다.</p>
|
||
<!-- 전문가 선택을 1단계로 이동 -->
|
||
<div class="form-group" style="margin-top: 15px;">
|
||
<label for="expert-select-step1">👨⚕️ 전문가 선택</label>
|
||
<select id="expert-select-step1" name="expert_id_display" class="form-control">
|
||
<option value="">선택 안 함 (전체 일정 보기)</option>
|
||
<?php
|
||
/* 전문가 목록 조회 (실제 DB 연동 필요)
|
||
$experts = get_experts();
|
||
foreach ($experts as $expert):
|
||
?>
|
||
<option value="<?php echo $expert['id']; ?>"><?php echo htmlspecialchars($expert['name']); ?></option>
|
||
<?php endforeach;
|
||
*/
|
||
?>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="calendar-container">
|
||
<div class="calendar-header">
|
||
<button type="button" class="calendar-nav prev-month" aria-label="이전 달"><span>‹</span></button>
|
||
<h3 class="calendar-title">
|
||
<span id="calendar-year"><?php echo $current_year; ?></span>년
|
||
<span id="calendar-month"><?php echo $current_month_num; ?></span>월
|
||
</h3>
|
||
<button type="button" class="calendar-nav next-month" aria-label="다음 달"><span>›</span></button>
|
||
</div>
|
||
<div class="calendar-grid">
|
||
<div class="calendar-weekdays">
|
||
<div class="weekday">일</div><div class="weekday">월</div><div class="weekday">화</div><div class="weekday">수</div><div class="weekday">목</div><div class="weekday">금</div><div class="weekday">토</div>
|
||
</div>
|
||
<div class="calendar-days" id="calendar-days"></div>
|
||
</div>
|
||
<div class="calendar-legend">
|
||
<div class="legend-item"><span class="legend-color available"></span><span>예약 가능</span></div>
|
||
<div class="legend-item"><span class="legend-color holiday"></span><span>휴일</span></div>
|
||
<div class="legend-item"><span class="legend-color full"></span><span>예약 마감</span></div>
|
||
<div class="legend-item"><span class="legend-color unavailable"></span><span>불가</span></div>
|
||
<div class="legend-item"><span class="legend-color selected"></span><span>선택</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="expert-visit-step-content" data-step="2" style="display: none;">
|
||
<div class="step-description">
|
||
<h4>🕐 방문 시간을 선택해주세요</h4>
|
||
<div class="selected-date-info"><strong>선택된 날짜: <span id="selected-date-display"></span></strong></div>
|
||
</div>
|
||
<div class="time-slots-container">
|
||
<div class="time-slots-grid" id="time-slots-grid"></div>
|
||
<div class="time-legend">
|
||
<div class="legend-item"><span class="legend-color time-available"></span><span>예약 가능</span></div>
|
||
<div class="legend-item"><span class="legend-color time-full"></span><span>예약 마감</span></div>
|
||
<div class="legend-item"><span class="legend-color time-too-soon"></span><span>예약 임박</span></div>
|
||
<div class="legend-item"><span class="legend-color time-selected"></span><span>선택</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="expert-visit-step-content" data-step="3" style="display: none;">
|
||
<div class="step-description">
|
||
<h4>📝 고객 정보를 입력해주세요</h4>
|
||
</div>
|
||
|
||
<div class="expert-visit-summary">
|
||
<h5>📋 예약 정보 확인</h5>
|
||
<div class="summary-grid">
|
||
<div class="summary-item"><span class="label">📅 예약 날짜:</span><span id="summary-date">-</span></div>
|
||
<div class="summary-item"><span class="label">🕐 예약 시간:</span><span id="summary-time">-</span></div>
|
||
<div class="summary-item"><span class="label">💰 방문 비용:</span><span><?php echo number_format($visit_fee); ?>원</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="customer-info-form">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="customer-name">👤 이름 <span class="required">*</span></label>
|
||
<input type="text" id="customer-name" name="customer_name" required placeholder="홍길동" value="<?php echo htmlspecialchars($customer_name); ?>">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="customer-phone">📱 연락처 <span class="required">*</span></label>
|
||
<input type="tel" id="customer-phone" name="customer_phone" required placeholder="010-1234-5678" value="<?php echo htmlspecialchars($customer_phone); ?>">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="customer-email">📧 이메일 <span class="required">*</span></label>
|
||
<input type="email" id="customer-email" name="customer_email" required placeholder="example@email.com" value="<?php echo htmlspecialchars($customer_email); ?>">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="expert-visit-type">🏠 방문 유형</label>
|
||
<select id="expert-visit-type" name="visit_type">
|
||
<option value="onsite">현장 방문</option>
|
||
<option value="online">온라인 상담</option>
|
||
<option value="phone">전화 상담</option>
|
||
</select>
|
||
</div>
|
||
<!-- 3단계 전문가 선택은 hidden으로 처리하고 1단계 값과 동기화 -->
|
||
<input type="hidden" id="expert-visit-resource" name="expert_id">
|
||
|
||
<div class="form-group">
|
||
<label for="customer-request">📝 요청사항</label>
|
||
<textarea id="customer-request" name="request_memo" rows="4" placeholder="방문 관련 요청사항이나 문의사항을 입력해주세요."></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<input type="hidden" id="selected-date" name="visit_date">
|
||
<input type="hidden" id="selected-time" name="visit_time">
|
||
<input type="hidden" name="payment_amount" value="<?php echo $visit_fee; ?>">
|
||
<input type="hidden" name="status" value="payment_pending">
|
||
<input type="hidden" name="wr_id" value="<?php echo $wr_id; ?>">
|
||
</div>
|
||
|
||
<div class="expert-visit-nav-buttons">
|
||
<button type="button" class="btn-prev" style="display: none;">← 이전</button>
|
||
<button type="button" class="btn-next">다음 →</button>
|
||
<button type="submit" class="btn-submit" style="display: none;">예약 신청</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const ExpertVisitPopup = {
|
||
elements: {},
|
||
state: {
|
||
currentStep: 1,
|
||
selectedDate: null,
|
||
selectedTime: null,
|
||
currentYear: new Date().getFullYear(),
|
||
currentMonth: new Date().getMonth() + 1,
|
||
expertId: '', // 전문가 ID 상태 추가
|
||
},
|
||
config: {
|
||
ajaxUrl: '<?php echo $ajax_url; ?>',
|
||
maxAdvanceDays: <?php echo (int)$max_advance_days; ?>,
|
||
},
|
||
|
||
init() {
|
||
this.elements.overlay = document.getElementById('expert-visit-popup-overlay');
|
||
if (!this.elements.overlay) return;
|
||
|
||
this.elements.popup = this.elements.overlay.querySelector('.expert-visit-modal-content');
|
||
this.elements.loading = this.elements.popup.querySelector('.loading-overlay');
|
||
this.elements.form = this.elements.popup.querySelector('#expert-visit-form');
|
||
this.elements.calendar = {
|
||
year: this.elements.popup.querySelector('#calendar-year'),
|
||
month: this.elements.popup.querySelector('#calendar-month'),
|
||
days: this.elements.popup.querySelector('#calendar-days'),
|
||
prevBtn: this.elements.popup.querySelector('.prev-month'),
|
||
nextBtn: this.elements.popup.querySelector('.next-month'),
|
||
};
|
||
this.elements.timeSlotsGrid = this.elements.popup.querySelector('#time-slots-grid');
|
||
this.elements.expertSelect = this.elements.popup.querySelector('#expert-select-step1');
|
||
this.elements.nav = {
|
||
prevBtn: this.elements.popup.querySelector('.btn-prev'),
|
||
nextBtn: this.elements.popup.querySelector('.btn-next'),
|
||
submitBtn: this.elements.popup.querySelector('.btn-submit'),
|
||
};
|
||
|
||
this.addEventListeners();
|
||
},
|
||
|
||
addEventListeners() {
|
||
const closeBtn = this.elements.popup.querySelector('.expert-visit-modal-close');
|
||
if (closeBtn) closeBtn.addEventListener('click', () => this.close());
|
||
|
||
this.elements.overlay.addEventListener('click', e => {
|
||
if (e.target === this.elements.overlay) this.close();
|
||
});
|
||
document.addEventListener('keydown', e => {
|
||
if (e.key === 'Escape' && this.elements.overlay.classList.contains('active')) this.close();
|
||
});
|
||
|
||
if (this.elements.calendar.prevBtn) this.elements.calendar.prevBtn.addEventListener('click', () => this.changeMonth(-1));
|
||
if (this.elements.calendar.nextBtn) this.elements.calendar.nextBtn.addEventListener('click', () => this.changeMonth(1));
|
||
|
||
if (this.elements.nav.prevBtn) this.elements.nav.prevBtn.addEventListener('click', () => this.goToStep(this.state.currentStep - 1));
|
||
if (this.elements.nav.nextBtn) this.elements.nav.nextBtn.addEventListener('click', () => this.goToNextStep());
|
||
if (this.elements.form) {
|
||
this.elements.form.addEventListener('submit', e => {
|
||
e.preventDefault();
|
||
this.submitForm();
|
||
});
|
||
}
|
||
|
||
// 전문가 선택 변경 시 달력 갱신
|
||
if (this.elements.expertSelect) {
|
||
this.elements.expertSelect.addEventListener('change', (e) => {
|
||
this.state.expertId = e.target.value;
|
||
this.state.selectedDate = null; // 날짜 선택 초기화
|
||
this.renderCalendar();
|
||
});
|
||
}
|
||
},
|
||
|
||
open() {
|
||
this.state.currentYear = new Date().getFullYear();
|
||
this.state.currentMonth = new Date().getMonth() + 1;
|
||
this.goToStep(1);
|
||
this.renderCalendar();
|
||
this.elements.overlay.classList.add('active');
|
||
document.body.style.overflow = 'hidden';
|
||
},
|
||
|
||
close() {
|
||
this.elements.overlay.classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
this.elements.form.reset();
|
||
this.state.selectedDate = null;
|
||
this.state.selectedTime = null;
|
||
this.state.expertId = '';
|
||
},
|
||
|
||
changeMonth(delta) {
|
||
this.state.currentMonth += delta;
|
||
if (this.state.currentMonth < 1) {
|
||
this.state.currentMonth = 12;
|
||
this.state.currentYear--;
|
||
} else if (this.state.currentMonth > 12) {
|
||
this.state.currentMonth = 1;
|
||
this.state.currentYear++;
|
||
}
|
||
this.renderCalendar();
|
||
},
|
||
|
||
async renderCalendar() {
|
||
this.elements.calendar.year.textContent = this.state.currentYear;
|
||
this.elements.calendar.month.textContent = this.state.currentMonth;
|
||
this.elements.calendar.days.innerHTML = '<div class="loading">달력 정보를 불러오는 중...</div>';
|
||
|
||
const availability = await this.fetchMonthAvailability();
|
||
if (!availability) {
|
||
this.elements.calendar.days.innerHTML = '<div class="error">달력 정보를 불러올 수 없습니다.</div>';
|
||
return;
|
||
}
|
||
|
||
this.elements.calendar.days.innerHTML = '';
|
||
const firstDay = new Date(this.state.currentYear, this.state.currentMonth - 1, 1);
|
||
const daysInMonth = new Date(this.state.currentYear, this.state.currentMonth, 0).getDate();
|
||
const startDayOfWeek = firstDay.getDay();
|
||
|
||
for (let i = 0; i < startDayOfWeek; i++) {
|
||
this.elements.calendar.days.appendChild(this.createDayElement(0, true));
|
||
}
|
||
|
||
for (let day = 1; day <= daysInMonth; day++) {
|
||
this.elements.calendar.days.appendChild(this.createDayElement(day, false, availability[day]));
|
||
}
|
||
},
|
||
|
||
createDayElement(day, isOtherMonth, availability = null) {
|
||
const dayElement = document.createElement('div');
|
||
dayElement.className = 'calendar-day';
|
||
if (isOtherMonth) {
|
||
dayElement.classList.add('other-month');
|
||
return dayElement;
|
||
}
|
||
|
||
dayElement.textContent = day;
|
||
|
||
const dateStr = `${this.state.currentYear}-${String(this.state.currentMonth).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
const currentDate = new Date(dateStr);
|
||
currentDate.setHours(0, 0, 0, 0);
|
||
|
||
if (currentDate.getTime() < today.getTime()) {
|
||
dayElement.classList.add('unavailable');
|
||
} else if (availability && availability.available) {
|
||
dayElement.classList.add('available');
|
||
// 이미 선택된 날짜라면 selected 클래스 추가
|
||
if (this.state.selectedDate === dateStr) {
|
||
dayElement.classList.add('selected');
|
||
}
|
||
dayElement.addEventListener('click', () => this.selectDate(dateStr, dayElement));
|
||
} else {
|
||
const reason = availability ? availability.reason : 'unavailable';
|
||
dayElement.classList.add(reason);
|
||
}
|
||
|
||
if (currentDate.getTime() === today.getTime()) {
|
||
dayElement.classList.add('today');
|
||
}
|
||
|
||
return dayElement;
|
||
},
|
||
|
||
selectDate(dateStr, element) {
|
||
const prevSelected = this.elements.calendar.days.querySelector('.selected');
|
||
if (prevSelected) prevSelected.classList.remove('selected');
|
||
element.classList.add('selected');
|
||
this.state.selectedDate = dateStr;
|
||
},
|
||
|
||
async fetchMonthAvailability() {
|
||
this.showLoading();
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('action', 'get_expert_visit_month_availability');
|
||
formData.append('year', this.state.currentYear);
|
||
formData.append('month', this.state.currentMonth);
|
||
formData.append('expert_id', this.state.expertId); // 전문가 ID 전송
|
||
|
||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||
const result = await response.json();
|
||
return result.success ? result.data : null;
|
||
} catch (error) {
|
||
console.error('Error fetching month availability:', error);
|
||
return null;
|
||
} finally {
|
||
this.hideLoading();
|
||
}
|
||
},
|
||
|
||
async fetchTimeSlots() {
|
||
this.showLoading();
|
||
this.elements.timeSlotsGrid.innerHTML = '<div class="loading">시간 정보를 불러오는 중...</div>';
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('action', 'get_expert_visit_time_slots');
|
||
formData.append('date', this.state.selectedDate);
|
||
formData.append('expert_id', this.state.expertId); // 전문가 ID 전송
|
||
|
||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
this.renderTimeSlots(result.data);
|
||
} else {
|
||
this.elements.timeSlotsGrid.innerHTML = `<div class="error">${result.message || '시간 정보를 불러올 수 없습니다.'}</div>`;
|
||
}
|
||
} catch (error) {
|
||
console.error('Error fetching time slots:', error);
|
||
this.elements.timeSlotsGrid.innerHTML = '<div class="error">오류가 발생했습니다. 다시 시도해주세요.</div>';
|
||
} finally {
|
||
this.hideLoading();
|
||
}
|
||
},
|
||
|
||
renderTimeSlots(slots) {
|
||
this.elements.timeSlotsGrid.innerHTML = '';
|
||
if (slots.length === 0) {
|
||
this.elements.timeSlotsGrid.innerHTML = '<div class="no-slots">해당 날짜에 운영되는 시간이 없습니다.</div>';
|
||
return;
|
||
}
|
||
|
||
let hasAvailableSlots = false;
|
||
slots.forEach(slot => {
|
||
const slotElement = document.createElement('div');
|
||
slotElement.className = 'time-slot';
|
||
|
||
let slotInfoText = '';
|
||
if (slot.available) {
|
||
hasAvailableSlots = true;
|
||
slotElement.classList.add('available');
|
||
slotElement.addEventListener('click', () => this.selectTime(slot.time, slotElement));
|
||
slotInfoText = `예약 ${slot.reserved_count} / ${slot.max_persons}`;
|
||
} else {
|
||
slotElement.classList.add(slot.reason || 'full');
|
||
slotInfoText = slot.reason === 'too_soon' ? '예약 임박' : '마감';
|
||
}
|
||
|
||
slotElement.innerHTML = `<span class="time-text">${slot.time}</span> <span class="slot-info">${slotInfoText}</span>`;
|
||
this.elements.timeSlotsGrid.appendChild(slotElement);
|
||
});
|
||
|
||
if (!hasAvailableSlots) {
|
||
const noSlotsMessage = document.createElement('div');
|
||
noSlotsMessage.className = 'no-slots';
|
||
noSlotsMessage.textContent = '현재 예약 가능한 시간이 없습니다. 다른 날짜를 선택해주세요.';
|
||
this.elements.timeSlotsGrid.prepend(noSlotsMessage);
|
||
}
|
||
},
|
||
|
||
selectTime(time, element) {
|
||
const prevSelected = this.elements.timeSlotsGrid.querySelector('.selected');
|
||
if (prevSelected) prevSelected.classList.remove('selected');
|
||
element.classList.add('selected');
|
||
this.state.selectedTime = time;
|
||
},
|
||
|
||
goToStep(step) {
|
||
this.state.currentStep = step;
|
||
this.elements.popup.querySelectorAll('.step').forEach((el, i) => {
|
||
el.classList.toggle('active', i + 1 === step);
|
||
el.classList.toggle('completed', i + 1 < step);
|
||
});
|
||
this.elements.popup.querySelectorAll('.expert-visit-step-content').forEach(el => {
|
||
el.style.display = parseInt(el.dataset.step) === step ? 'block' : 'none';
|
||
});
|
||
|
||
this.elements.nav.prevBtn.style.display = step > 1 ? 'inline-block' : 'none';
|
||
this.elements.nav.nextBtn.style.display = step < 3 ? 'inline-block' : 'none';
|
||
this.elements.nav.submitBtn.style.display = step === 3 ? 'inline-block' : 'none';
|
||
},
|
||
|
||
goToNextStep() {
|
||
if (this.state.currentStep === 1 && !this.state.selectedDate) {
|
||
alert('날짜를 선택해주세요.'); return;
|
||
}
|
||
if (this.state.currentStep === 2 && !this.state.selectedTime) {
|
||
alert('시간을 선택해주세요.'); return;
|
||
}
|
||
|
||
if (this.state.currentStep < 3) {
|
||
this.goToStep(this.state.currentStep + 1);
|
||
if (this.state.currentStep === 2) this.fetchTimeSlots();
|
||
if (this.state.currentStep === 3) this.updateSummary();
|
||
}
|
||
},
|
||
|
||
updateSummary() {
|
||
const date = new Date(this.state.selectedDate);
|
||
const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' };
|
||
this.elements.popup.querySelector('#summary-date').textContent = date.toLocaleDateString('ko-KR', options);
|
||
this.elements.popup.querySelector('#summary-time').textContent = this.state.selectedTime;
|
||
this.elements.popup.querySelector('#selected-date-display').textContent = date.toLocaleDateString('ko-KR', options);
|
||
this.elements.form.querySelector('#selected-date').value = this.state.selectedDate;
|
||
this.elements.form.querySelector('#selected-time').value = this.state.selectedTime;
|
||
|
||
// 전문가 ID 동기화
|
||
this.elements.form.querySelector('#expert-visit-resource').value = this.state.expertId;
|
||
},
|
||
|
||
async submitForm() {
|
||
if (!this.elements.form.checkValidity()) {
|
||
alert('필수 입력 항목을 모두 채워주세요.');
|
||
this.elements.form.reportValidity();
|
||
return;
|
||
}
|
||
this.showLoading();
|
||
this.elements.nav.submitBtn.disabled = true;
|
||
this.elements.nav.submitBtn.textContent = '처리 중...';
|
||
|
||
try {
|
||
const formData = new FormData(this.elements.form);
|
||
const response = await fetch('<?php echo $form_action_url; ?>', { method: 'POST', body: formData });
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
alert('예약 신청이 완료되었습니다.');
|
||
this.close();
|
||
// location.reload();
|
||
} else {
|
||
alert(result.message || '예약 처리 중 오류가 발생했습니다.');
|
||
}
|
||
} catch (error) {
|
||
console.error('Form submission error:', error);
|
||
alert('네트워크 오류가 발생했습니다. 다시 시도해주세요.');
|
||
} finally {
|
||
this.hideLoading();
|
||
this.elements.nav.submitBtn.disabled = false;
|
||
this.elements.nav.submitBtn.textContent = '예약 신청';
|
||
}
|
||
},
|
||
|
||
showLoading() { if (this.elements.loading) this.elements.loading.style.display = 'flex'; },
|
||
hideLoading() { if (this.elements.loading) this.elements.loading.style.display = 'none'; },
|
||
};
|
||
|
||
document.addEventListener('DOMContentLoaded', () => ExpertVisitPopup.init());
|
||
|
||
function openExpertVisitPopup(wr_id = 0) {
|
||
if (wr_id) {
|
||
const form = ExpertVisitPopup.elements.form;
|
||
if(form) {
|
||
let wrIdField = form.querySelector('input[name="wr_id"]');
|
||
if (!wrIdField) {
|
||
wrIdField = document.createElement('input');
|
||
wrIdField.type = 'hidden';
|
||
wrIdField.name = 'wr_id';
|
||
form.appendChild(wrIdField);
|
||
}
|
||
wrIdField.value = wr_id;
|
||
}
|
||
}
|
||
ExpertVisitPopup.open();
|
||
}
|
||
</script>
|