500 lines
17 KiB
PHP
500 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* 예약 관리
|
|
*/
|
|
$sub_menu = '850200';
|
|
include_once('./_common.php');
|
|
|
|
// 관리자 권한 확인
|
|
if (!$is_admin) {
|
|
alert('관리자만 접근할 수 있습니다.');
|
|
}
|
|
|
|
// 설치 확인
|
|
if (!is_consultant_installed()) {
|
|
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
|
}
|
|
|
|
$g5['title'] = '예약 관리';
|
|
|
|
// 필터 파라미터
|
|
$status = $_GET['status'] ?? '';
|
|
$date = $_GET['date'] ?? '';
|
|
$search = $_GET['search'] ?? '';
|
|
$page = (int)($_GET['page'] ?? 1);
|
|
$per_page = 20;
|
|
|
|
// 상태 변경 처리
|
|
if ($_POST['action'] == 'update_status') {
|
|
$reservation_id = (int)$_POST['reservation_id'];
|
|
$new_status = $_POST['new_status'];
|
|
$memo = $_POST['memo'] ?? '';
|
|
$send_sms = isset($_POST['send_sms']) && $_POST['send_sms'] == '1';
|
|
$send_email = isset($_POST['send_email']) && $_POST['send_email'] == '1';
|
|
|
|
if ($reservation_id && $new_status) {
|
|
// 기존 상태 조회
|
|
$old_res = sql_fetch("SELECT * FROM consultant_reservations WHERE id = {$reservation_id}");
|
|
|
|
$sql = "UPDATE consultant_reservations
|
|
SET status = '" . sql_real_escape_string($new_status) . "',
|
|
admin_memo = '" . sql_real_escape_string($memo) . "',
|
|
updated_at = NOW()
|
|
WHERE id = {$reservation_id}";
|
|
|
|
if (sql_query($sql)) {
|
|
// 💡 [추가] 알림 발송 로직
|
|
if ($send_sms || $send_email) {
|
|
// 템플릿 키 결정
|
|
$template_key = '';
|
|
if ($new_status == 'reserved') {
|
|
$template_key = 'consultant_confirmed_customer';
|
|
} elseif ($new_status == 'cancelled') {
|
|
$template_key = 'consultant_cancelled_customer';
|
|
}
|
|
|
|
if ($template_key) {
|
|
// 알림 데이터 준비
|
|
$noti_data = [
|
|
'customer_name' => $old_res['customer_name'],
|
|
'customer_phone' => $old_res['customer_phone'],
|
|
'customer_email' => $old_res['customer_email'],
|
|
'reservation_date' => $old_res['reservation_date'],
|
|
'reservation_time' => substr($old_res['reservation_time'], 0, 5),
|
|
'payment_amount' => number_format($old_res['payment_amount']),
|
|
'cancel_reason' => $memo // 취소 사유로 메모 사용
|
|
];
|
|
|
|
if ($send_sms) {
|
|
consultant_send_notification('sms', $template_key, $noti_data);
|
|
}
|
|
if ($send_email) {
|
|
consultant_send_notification('email', $template_key, $noti_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
alert('상태가 변경되었습니다.', $_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING']);
|
|
} else {
|
|
alert('상태 변경에 실패했습니다.');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 검색 조건 구성
|
|
$where_conditions = ["is_deleted = 0"];
|
|
|
|
if ($status) {
|
|
$where_conditions[] = "status = '" . sql_real_escape_string($status) . "'";
|
|
}
|
|
|
|
if ($date) {
|
|
$where_conditions[] = "reservation_date = '" . sql_real_escape_string($date) . "'";
|
|
}
|
|
|
|
if ($search) {
|
|
$search_escaped = sql_real_escape_string($search);
|
|
$where_conditions[] = "(customer_name LIKE '%{$search_escaped}%' OR customer_phone LIKE '%{$search_escaped}%')";
|
|
}
|
|
|
|
$where_clause = implode(' AND ', $where_conditions);
|
|
|
|
// 전체 개수 조회
|
|
$count_sql = "SELECT COUNT(*) as total FROM consultant_reservations WHERE {$where_clause}";
|
|
$count_result = sql_fetch($count_sql);
|
|
$total = $count_result['total'];
|
|
|
|
// 페이징 계산
|
|
$offset = ($page - 1) * $per_page;
|
|
$total_pages = ceil($total / $per_page);
|
|
|
|
// 예약 목록 조회
|
|
$sql = "SELECT * FROM consultant_reservations
|
|
WHERE {$where_clause}
|
|
ORDER BY created_at DESC
|
|
LIMIT {$offset}, {$per_page}";
|
|
|
|
$reservations = [];
|
|
$result = sql_query($sql);
|
|
while ($row = sql_fetch_array($result)) {
|
|
$reservations[] = $row;
|
|
}
|
|
|
|
// 상태 라벨
|
|
$status_labels = [
|
|
'payment_pending' => '입금대기',
|
|
'reserved' => '예약확정',
|
|
'completed' => '상담완료',
|
|
'cancelled' => '예약취소'
|
|
];
|
|
|
|
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
|
?>
|
|
|
|
<style>
|
|
.reservations-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
.filter-form {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.reservations-table {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table-header {
|
|
background: #fff;
|
|
padding: 15px 20px;
|
|
font-weight: bold;
|
|
border-bottom: 1px solid #ddd;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th, td {
|
|
padding: 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
th {
|
|
background: #fff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.status-badge {
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 11px;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.btn {
|
|
padding: 6px 12px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
margin: 2px;
|
|
}
|
|
|
|
.btn-primary { background: #007bff; color: white; }
|
|
.btn-success { background: #28a745; color: white; }
|
|
.btn-warning { background: #ffc107; color: #212529; }
|
|
.btn-danger { background: #dc3545; color: white; }
|
|
.btn-secondary { background: #6c757d; color: white; }
|
|
.btn-info { background: #17a2b8; color: white; } /* 견적작성 버튼용 */
|
|
|
|
.form-control {
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.pagination a,
|
|
.pagination span {
|
|
padding: 8px 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
text-decoration: none;
|
|
color: #333;
|
|
}
|
|
|
|
.pagination .current {
|
|
background: #007bff;
|
|
color: white;
|
|
border-color: #007bff;
|
|
}
|
|
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 1000;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0,0,0,0.5);
|
|
}
|
|
|
|
.modal-content {
|
|
background-color: white;
|
|
margin: 15% auto;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
width: 400px;
|
|
max-width: 90%;
|
|
}
|
|
|
|
.close {
|
|
color: #aaa;
|
|
float: right;
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.close:hover {
|
|
color: black;
|
|
}
|
|
|
|
.no-data {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.filter-form {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
table {
|
|
font-size: 12px;
|
|
}
|
|
|
|
th, td {
|
|
padding: 8px 4px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="reservations-container">
|
|
<h2><?php echo $g5['title']; ?></h2>
|
|
|
|
<!-- 필터 폼 -->
|
|
<form method="get" class="filter-form">
|
|
<select name="status" class="form-control">
|
|
<option value="">전체 상태</option>
|
|
<?php foreach ($status_labels as $key => $label): ?>
|
|
<option value="<?php echo $key; ?>" <?php echo $status == $key ? 'selected' : ''; ?>>
|
|
<?php echo $label; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<input type="date" name="date" value="<?php echo $date; ?>" class="form-control" placeholder="예약일">
|
|
|
|
<input type="text" name="search" value="<?php echo htmlspecialchars($search); ?>"
|
|
class="form-control" placeholder="고객명 또는 연락처 검색">
|
|
|
|
<button type="submit" class="btn btn-primary">검색</button>
|
|
<a href="<?php echo $_SERVER['PHP_SELF']; ?>" class="btn btn-secondary">초기화</a>
|
|
<a href="dashboard.php" class="btn btn-secondary">대시보드로</a>
|
|
</form>
|
|
|
|
<!-- 예약 목록 -->
|
|
<div class="reservations-table">
|
|
<div class="table-header">
|
|
<span>예약 목록 (총 <?php echo number_format($total); ?>건)</span>
|
|
<span>페이지 <?php echo $page; ?> / <?php echo $total_pages; ?></span>
|
|
</div>
|
|
|
|
<?php if (!empty($reservations)): ?>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>예약번호</th>
|
|
<th>고객정보</th>
|
|
<th>예약일시</th>
|
|
<th>상담유형</th>
|
|
<th>상담비</th>
|
|
<th>상태</th>
|
|
<th>신청일</th>
|
|
<th>관리</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($reservations as $reservation): ?>
|
|
<tr>
|
|
<td>#<?php echo $reservation['id']; ?></td>
|
|
<td>
|
|
<strong><?php echo htmlspecialchars($reservation['customer_name']); ?></strong><br>
|
|
<small><?php echo htmlspecialchars($reservation['customer_phone']); ?></small><br>
|
|
<small><?php echo htmlspecialchars($reservation['customer_email']); ?></small>
|
|
</td>
|
|
<td>
|
|
<?php echo $reservation['reservation_date']; ?><br>
|
|
<small><?php echo substr($reservation['reservation_time'], 0, 5); ?></small>
|
|
</td>
|
|
<td>
|
|
<?php
|
|
$types = ['onsite' => '현장', 'online' => '온라인', 'phone' => '전화'];
|
|
echo $types[$reservation['consultation_type']] ?? $reservation['consultation_type'];
|
|
?>
|
|
</td>
|
|
<td><?php echo number_format($reservation['payment_amount']); ?>원</td>
|
|
<td>
|
|
<span class="status-badge status-<?php echo $reservation['status']; ?>">
|
|
<?php echo $status_labels[$reservation['status']] ?? $reservation['status']; ?>
|
|
</span>
|
|
</td>
|
|
<td><?php echo date('m-d H:i', strtotime($reservation['created_at'])); ?></td>
|
|
<td>
|
|
<button onclick="openStatusModal(<?php echo $reservation['id']; ?>, '<?php echo $reservation['status']; ?>')" class="btn btn-warning btn-sm">상태변경</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php else: ?>
|
|
<div class="no-data">
|
|
검색 조건에 맞는 예약이 없습니다.
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- 페이징 -->
|
|
<?php if ($total_pages > 1): ?>
|
|
<div class="pagination">
|
|
<?php if ($page > 1): ?>
|
|
<a href="?<?php echo http_build_query(array_merge($_GET, ['page' => $page - 1])); ?>">이전</a>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
$start_page = max(1, $page - 5);
|
|
$end_page = min($total_pages, $page + 5);
|
|
|
|
for ($i = $start_page; $i <= $end_page; $i++):
|
|
?>
|
|
<?php if ($i == $page): ?>
|
|
<span class="current"><?php echo $i; ?></span>
|
|
<?php else: ?>
|
|
<a href="?<?php echo http_build_query(array_merge($_GET, ['page' => $i])); ?>"><?php echo $i; ?></a>
|
|
<?php endif; ?>
|
|
<?php endfor; ?>
|
|
|
|
<?php if ($page < $total_pages): ?>
|
|
<a href="?<?php echo http_build_query(array_merge($_GET, ['page' => $page + 1])); ?>">다음</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- 상태 변경 모달 -->
|
|
<div id="statusModal" class="modal">
|
|
<div class="modal-content">
|
|
<span class="close">×</span>
|
|
<h3>예약 상태 변경</h3>
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="update_status">
|
|
<input type="hidden" name="reservation_id" id="modal_reservation_id">
|
|
|
|
<div style="margin: 15px 0;">
|
|
<label for="modal_new_status">변경할 상태</label>
|
|
<select name="new_status" id="modal_new_status" class="form-control" required>
|
|
<?php foreach ($status_labels as $key => $label): ?>
|
|
<option value="<?php echo $key; ?>"><?php echo $label; ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div style="margin: 15px 0;">
|
|
<label for="modal_memo">관리자 메모 (선택)</label>
|
|
<textarea name="memo" rows="3" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"></textarea>
|
|
</div>
|
|
|
|
<!-- 💡 [추가] 알림 발송 옵션 -->
|
|
<div style="margin: 15px 0; background: #f8f9fa; padding: 10px; border-radius: 4px;">
|
|
<div style="font-weight: bold; margin-bottom: 5px;">알림 발송</div>
|
|
<label style="margin-right: 15px;">
|
|
<input type="checkbox" name="send_sms" value="1" checked> 문자(SMS) 발송
|
|
</label>
|
|
<label>
|
|
<input type="checkbox" name="send_email" value="1" checked> 이메일 발송
|
|
</label>
|
|
<div style="font-size: 12px; color: #666; margin-top: 5px;">
|
|
* 예약확정/취소 상태 변경 시에만 발송됩니다.
|
|
</div>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin-top: 20px;">
|
|
<button type="submit" class="btn btn-primary">확인</button>
|
|
<button type="button" onclick="closeModal()" class="btn btn-secondary">취소</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openStatusModal(reservationId, currentStatus) {
|
|
document.getElementById('modal_reservation_id').value = reservationId;
|
|
document.getElementById('modal_new_status').value = currentStatus;
|
|
document.getElementById('statusModal').style.display = 'block';
|
|
}
|
|
|
|
function closeModal() {
|
|
document.getElementById('statusModal').style.display = 'none';
|
|
}
|
|
|
|
// 모달 외부 클릭시 닫기
|
|
window.onclick = function(event) {
|
|
const modal = document.getElementById('statusModal');
|
|
if (event.target == modal) {
|
|
modal.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// 닫기 버튼
|
|
document.querySelector('.close').onclick = function() {
|
|
closeModal();
|
|
}
|
|
</script>
|
|
|
|
<?php
|
|
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
|
?>
|