first commit 2
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
<?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');
|
||||
?>
|
||||
Reference in New Issue
Block a user