first commit 2
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
// AJAX 요청 처리
|
||||
if (isset($_POST['action'])) {
|
||||
header('Content-Type: application/json');
|
||||
include_once('../_common_con.php'); // 💡 [수정] 컴포넌트용 공통 파일 포함
|
||||
include_once('../lib/notification_helper.php');
|
||||
if (!$is_member) {
|
||||
echo json_encode(['success' => false, 'message' => '로그인이 필요합니다.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
$reservation_id = (int)($_POST['id'] ?? 0);
|
||||
|
||||
try {
|
||||
if ($action === 'cancel') {
|
||||
if (!$reservation_id) {
|
||||
throw new Exception('예약 ID가 없습니다.');
|
||||
}
|
||||
|
||||
// 본인의 예약인지 확인
|
||||
$sql = "SELECT * FROM expert_visit_reservations WHERE id = '{$reservation_id}' AND (customer_id = '{$member['mb_id']}' OR customer_phone = '{$member['mb_hp']}')";
|
||||
$reservation = sql_fetch($sql);
|
||||
|
||||
if (!$reservation) {
|
||||
throw new Exception('취소할 수 있는 예약 정보가 없거나 권한이 없습니다.');
|
||||
}
|
||||
|
||||
// 이미 취소되었거나 완료된 예약은 변경 불가
|
||||
if ($reservation['status'] === 'cancelled' || $reservation['status'] === 'completed') {
|
||||
throw new Exception('이미 취소되었거나 완료된 예약입니다.');
|
||||
}
|
||||
|
||||
// 예약 취소 처리
|
||||
$sql_update = "UPDATE expert_visit_reservations
|
||||
SET status = 'cancelled',
|
||||
updated_at = NOW(),
|
||||
updated_by = '{$member['mb_id']}'
|
||||
WHERE id = '{$reservation_id}'";
|
||||
|
||||
if (sql_query($sql_update)) {
|
||||
// 예약 취소 알림 발송
|
||||
if (function_exists('notify_for_expert_visit')) {
|
||||
notify_for_expert_visit($reservation_id, '예약취소');
|
||||
}
|
||||
echo json_encode(['success' => true, 'message' => '예약이 정상적으로 취소되었습니다.']);
|
||||
} else {
|
||||
throw new Exception('예약 취소 중 오류가 발생했습니다.');
|
||||
}
|
||||
|
||||
} elseif ($action === 'get_list') {
|
||||
// 예약 목록 조회
|
||||
$sql = "SELECT *
|
||||
FROM expert_visit_reservations
|
||||
WHERE (customer_id = '{$member['mb_id']}' OR customer_phone = '{$member['mb_hp']}')
|
||||
AND is_deleted = 0
|
||||
ORDER BY visit_date DESC, visit_time DESC";
|
||||
$result = sql_query($sql);
|
||||
$reservations = [];
|
||||
while($row = sql_fetch_array($result)) {
|
||||
$row['status_text'] = get_order_config('reservation_status_' . $row['status'], $row['status']);
|
||||
$row['is_cancellable'] = !in_array($row['status'], ['completed', 'cancelled']);
|
||||
$reservations[] = $row;
|
||||
}
|
||||
echo json_encode(['success' => true, 'data' => $reservations]);
|
||||
} else {
|
||||
throw new Exception('잘못된 요청입니다.');
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
$ajax_url = G5_ADMIN_URL . '/order_manage/components/my_reservations_popup.php';
|
||||
include_once(G5_ADMIN_URL . '/order_manage/_common_con.php');
|
||||
include_once(G5_ADMIN_URL . '/order_manage/lib/notification_helper.php');
|
||||
?>
|
||||
|
||||
<!-- 나의 예약 현황 팝업 -->
|
||||
<div id="my-reservations-popup" class="reservation-modal-overlay">
|
||||
<div class="reservation-modal-content">
|
||||
<div class="reservation-modal-header">
|
||||
<h2>나의 예약 현황</h2>
|
||||
<button type="button" class="reservation-modal-close" aria-label="팝업 닫기">×</button>
|
||||
</div>
|
||||
<div class="reservation-modal-body">
|
||||
<div class="loading-overlay" style="display: none;">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div id="reservation-results">
|
||||
<!-- 예약 목록이 여기에 표시됩니다. -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 팝업 공통 스타일 (expert_visit_popup.php와 통일) */
|
||||
.reservation-modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); z-index: 9999; overflow-y: auto; }
|
||||
.reservation-modal-overlay.active { display: flex; align-items: center; justify-content: center; padding: 20px; }
|
||||
.reservation-modal-content { background: #fff; border-radius: 10px; width: 100%; max-width: 600px; max-height: 90vh; overflow-y: auto; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); display: flex; flex-direction: column; position: relative; }
|
||||
.reservation-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 25px; border-bottom: 1px solid #eee; background: #f8f9fa; border-radius: 10px 10px 0 0; }
|
||||
.reservation-modal-header h2 { margin: 0; font-size: 20px; font-weight: 600; color: #333; }
|
||||
.reservation-modal-close { background: none; border: none; font-size: 24px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s ease; }
|
||||
.reservation-modal-close:hover { background: #e9ecef; color: #333; }
|
||||
.reservation-modal-body { padding: 25px; }
|
||||
|
||||
/* 로딩 스피너 */
|
||||
.loading-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.8); display: flex; align-items: center; justify-content: center; z-index: 10; }
|
||||
.loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #007bff; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; }
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
|
||||
/* 예약 목록 스타일 */
|
||||
#reservation-results { margin-top: 0; }
|
||||
.reservation-item { border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin-bottom: 15px; background: #fff; }
|
||||
.reservation-item-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 10px; }
|
||||
.reservation-status { padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; }
|
||||
.status-payment_pending { background: #fff3cd; color: #856404; }
|
||||
.status-reserved { background: #d4edda; color: #155724; }
|
||||
.status-completed { background: #cce5ff; color: #004085; }
|
||||
.status-cancelled { background: #f8d7da; color: #721c24; }
|
||||
|
||||
.reservation-details-grid { margin: 15px 0; font-size: 15px; line-height: 1.6; }
|
||||
.detail-item { display: flex; margin-bottom: 10px; }
|
||||
.detail-item .label { font-weight: 600; color: #555; width: 90px; flex-shrink: 0; }
|
||||
.detail-item .value { color: #333; }
|
||||
|
||||
.reservation-actions { margin-top: 20px; text-align: right; }
|
||||
.btn-danger { background: #dc3545; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 600; }
|
||||
.btn-danger:hover { background: #c82333; }
|
||||
.cancel-notice { font-size: 13px; color: #666; margin-top: 15px; text-align: right; padding: 10px; background-color: #f8f9fa; border-radius: 4px; }
|
||||
.no-results { text-align: center; color: #666; padding: 40px 0; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const MyReservationsPopup = {
|
||||
elements: {},
|
||||
config: {
|
||||
ajaxUrl: '<?php echo $ajax_url; ?>'
|
||||
},
|
||||
|
||||
init() {
|
||||
this.elements.popup = document.getElementById('my-reservations-popup');
|
||||
if (!this.elements.popup) return;
|
||||
|
||||
this.elements.loading = this.elements.popup.querySelector('.loading-overlay');
|
||||
this.elements.resultsContainer = this.elements.popup.querySelector('#reservation-results');
|
||||
|
||||
this.addEventListeners();
|
||||
},
|
||||
|
||||
addEventListeners() {
|
||||
const closeBtn = this.elements.popup.querySelector('.reservation-modal-close');
|
||||
if (closeBtn) closeBtn.addEventListener('click', () => this.close());
|
||||
|
||||
this.elements.popup.addEventListener('click', e => {
|
||||
if (e.target === this.elements.popup) this.close();
|
||||
});
|
||||
|
||||
if (this.elements.resultsContainer) {
|
||||
this.elements.resultsContainer.addEventListener('click', e => {
|
||||
if (e.target.classList.contains('btn-cancel-reservation')) {
|
||||
const reservationId = e.target.dataset.id;
|
||||
if (confirm('정말 이 예약을 취소하시겠습니까?')) {
|
||||
this.cancelReservation(reservationId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
open() {
|
||||
if (!this.elements.popup) return;
|
||||
this.elements.popup.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
this.getReservations(); // 팝업 열릴 때 목록 조회
|
||||
},
|
||||
|
||||
close() {
|
||||
if (!this.elements.popup) return;
|
||||
this.elements.popup.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
},
|
||||
|
||||
async getReservations() {
|
||||
this.showLoading();
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'get_list');
|
||||
|
||||
try {
|
||||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
this.renderResults(result.data);
|
||||
} else {
|
||||
this.elements.resultsContainer.innerHTML = `<p class="no-results">${result.message}</p>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("조회 오류:", error);
|
||||
this.elements.resultsContainer.innerHTML = '<p class="no-results">조회 중 오류가 발생했습니다.</p>';
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
renderResults(reservations) {
|
||||
if (reservations.length === 0) {
|
||||
this.elements.resultsContainer.innerHTML = '<p class="no-results">예약 내역이 없습니다.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
reservations.forEach(res => {
|
||||
const date = new Date(res.visit_date + ' ' + res.visit_time);
|
||||
const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long', hour: '2-digit', minute: '2-digit' };
|
||||
const formattedDate = date.toLocaleDateString('ko-KR', options);
|
||||
|
||||
html += `
|
||||
<div class="reservation-item" id="reservation-${res.id}">
|
||||
<div class="reservation-item-header">
|
||||
<strong>예약 번호: #${res.id}</strong>
|
||||
<span class="reservation-status status-${res.status}">${res.status_text}</span>
|
||||
</div>
|
||||
<div class="reservation-details-grid">
|
||||
<div class="detail-item"><span class="label">방문 일시:</span><span class="value">${formattedDate}</span></div>
|
||||
<div class="detail-item"><span class="label">방문 지역:</span><span class="value">${res.temp_3 || '-'}</span></div>
|
||||
<div class="detail-item"><span class="label">방문 비용:</span><span class="value">${Number(res.payment_amount).toLocaleString()}원</span></div>
|
||||
</div>
|
||||
<div class="reservation-actions">`;
|
||||
|
||||
if (res.is_cancellable) {
|
||||
html += `<button type="button" class="btn-danger btn-cancel-reservation" data-id="${res.id}">예약 취소</button>`;
|
||||
} else if (res.status !== 'cancelled' && res.status !== 'completed') {
|
||||
html += `<p class="cancel-notice">취소 불가 상태입니다.</p>`;
|
||||
}
|
||||
html += `</div></div>`;
|
||||
});
|
||||
this.elements.resultsContainer.innerHTML = html;
|
||||
},
|
||||
|
||||
async cancelReservation(reservationId) {
|
||||
this.showLoading();
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'cancel');
|
||||
formData.append('id', reservationId);
|
||||
|
||||
try {
|
||||
const response = await fetch(this.config.ajaxUrl, { method: 'POST', body: formData });
|
||||
const result = await response.json();
|
||||
alert(result.message);
|
||||
if (result.success) {
|
||||
await this.getReservations(); // 목록 새로고침
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("취소 오류:", error);
|
||||
alert('예약 취소 처리 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
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', () => MyReservationsPopup.init());
|
||||
|
||||
function openMyExpertReservationsPopup() {
|
||||
MyReservationsPopup.open();
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user