454 lines
13 KiB
PHP
454 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* 상담 예약 통계 분석
|
|
*/
|
|
$sub_menu = '850400';
|
|
include_once('./_common.php');
|
|
|
|
// 관리자 권한 확인
|
|
if (!$is_admin) {
|
|
alert('관리자만 접근할 수 있습니다.');
|
|
}
|
|
|
|
// 설치 확인
|
|
if (!is_consultant_installed()) {
|
|
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
|
}
|
|
|
|
$g5['title'] = '통계 분석';
|
|
|
|
// 기간 설정
|
|
$start_date = $_GET['start_date'] ?? date('Y-m-01'); // 이번 달 첫날
|
|
$end_date = $_GET['end_date'] ?? date('Y-m-d'); // 오늘
|
|
|
|
// 전체 통계
|
|
$sql = "SELECT
|
|
COUNT(*) as total,
|
|
COUNT(CASE WHEN status = 'payment_pending' THEN 1 END) as pending,
|
|
COUNT(CASE WHEN status = 'reserved' THEN 1 END) as confirmed,
|
|
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
|
|
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled,
|
|
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as total_revenue
|
|
FROM consultant_reservations
|
|
WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}'
|
|
AND is_deleted = 0";
|
|
$total_stats = sql_fetch($sql);
|
|
|
|
// 일별 통계
|
|
$sql = "SELECT
|
|
reservation_date,
|
|
COUNT(*) as total,
|
|
COUNT(CASE WHEN status = 'payment_pending' THEN 1 END) as pending,
|
|
COUNT(CASE WHEN status = 'reserved' THEN 1 END) as confirmed,
|
|
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed,
|
|
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled,
|
|
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as revenue
|
|
FROM consultant_reservations
|
|
WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}'
|
|
AND is_deleted = 0
|
|
GROUP BY reservation_date
|
|
ORDER BY reservation_date DESC";
|
|
$daily_stats = [];
|
|
$result = sql_query($sql);
|
|
while ($row = sql_fetch_array($result)) {
|
|
$daily_stats[] = $row;
|
|
}
|
|
|
|
// 시간대별 통계
|
|
$sql = "SELECT
|
|
reservation_time,
|
|
COUNT(*) as count
|
|
FROM consultant_reservations
|
|
WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}'
|
|
AND is_deleted = 0
|
|
GROUP BY reservation_time
|
|
ORDER BY reservation_time";
|
|
$time_stats = [];
|
|
$result = sql_query($sql);
|
|
while ($row = sql_fetch_array($result)) {
|
|
$time_stats[] = $row;
|
|
}
|
|
|
|
// 상태별 통계
|
|
$status_labels = [
|
|
'payment_pending' => '입금대기',
|
|
'reserved' => '예약확정',
|
|
'completed' => '상담완료',
|
|
'cancelled' => '예약취소'
|
|
];
|
|
|
|
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
|
?>
|
|
|
|
<style>
|
|
.statistics-container {
|
|
max-width: 1200px;
|
|
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;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 32px;
|
|
font-weight: bold;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #666;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.stat-card.total .stat-number {
|
|
color: #007bff;
|
|
}
|
|
|
|
.stat-card.pending .stat-number {
|
|
color: #ffc107;
|
|
}
|
|
|
|
.stat-card.confirmed .stat-number {
|
|
color: #28a745;
|
|
}
|
|
|
|
.stat-card.completed .stat-number {
|
|
color: #17a2b8;
|
|
}
|
|
|
|
.stat-card.cancelled .stat-number {
|
|
color: #dc3545;
|
|
}
|
|
|
|
.stat-card.revenue .stat-number {
|
|
color: #6f42c1;
|
|
}
|
|
|
|
.chart-container {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.chart-title {
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
margin-bottom: 20px;
|
|
color: #333;
|
|
}
|
|
|
|
.table-container {
|
|
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;
|
|
}
|
|
|
|
.table-content {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th,
|
|
td {
|
|
padding: 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
th {
|
|
background: #fff;
|
|
font-weight: bold;
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
|
|
.btn {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #6c757d;
|
|
color: white;
|
|
}
|
|
|
|
.form-control {
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.bar-chart {
|
|
display: flex;
|
|
align-items: end;
|
|
height: 200px;
|
|
gap: 10px;
|
|
padding: 20px 0;
|
|
}
|
|
|
|
.bar {
|
|
background: #007bff;
|
|
border-radius: 4px 4px 0 0;
|
|
min-width: 40px;
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: end;
|
|
align-items: center;
|
|
}
|
|
|
|
.bar-value {
|
|
position: absolute;
|
|
top: -25px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.bar-label {
|
|
margin-top: 10px;
|
|
font-size: 11px;
|
|
color: #666;
|
|
transform: rotate(-45deg);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.no-data {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.filter-form {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="statistics-container">
|
|
<h2><?php echo $g5['title']; ?></h2>
|
|
|
|
<!-- 기간 필터 -->
|
|
<form method="get" class="filter-form">
|
|
<label>
|
|
시작일:
|
|
<input type="date" name="start_date" value="<?php echo $start_date; ?>" class="form-control">
|
|
</label>
|
|
|
|
<label>
|
|
종료일:
|
|
<input type="date" name="end_date" value="<?php echo $end_date; ?>" class="form-control">
|
|
</label>
|
|
|
|
<button type="submit" class="btn btn-primary">조회</button>
|
|
<a href="dashboard.php" class="btn btn-secondary">대시보드로</a>
|
|
</form>
|
|
|
|
<!-- 전체 통계 -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card total">
|
|
<div class="stat-number"><?php echo number_format($total_stats['total']); ?></div>
|
|
<div class="stat-label">전체 예약</div>
|
|
</div>
|
|
|
|
<div class="stat-card pending">
|
|
<div class="stat-number"><?php echo number_format($total_stats['pending']); ?></div>
|
|
<div class="stat-label">입금대기</div>
|
|
</div>
|
|
|
|
<div class="stat-card confirmed">
|
|
<div class="stat-number"><?php echo number_format($total_stats['confirmed']); ?></div>
|
|
<div class="stat-label">예약확정</div>
|
|
</div>
|
|
|
|
<div class="stat-card completed">
|
|
<div class="stat-number"><?php echo number_format($total_stats['completed']); ?></div>
|
|
<div class="stat-label">상담완료</div>
|
|
</div>
|
|
|
|
<div class="stat-card cancelled">
|
|
<div class="stat-number"><?php echo number_format($total_stats['cancelled']); ?></div>
|
|
<div class="stat-label">예약취소</div>
|
|
</div>
|
|
|
|
<div class="stat-card revenue">
|
|
<div class="stat-number"><?php echo number_format($total_stats['total_revenue']); ?>원</div>
|
|
<div class="stat-label">총 매출</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 시간대별 예약 현황 -->
|
|
<?php if (!empty($time_stats)): ?>
|
|
<div class="chart-container">
|
|
<div class="chart-title">시간대별 예약 현황</div>
|
|
<div class="bar-chart">
|
|
<?php
|
|
$max_count = max(array_column($time_stats, 'count'));
|
|
foreach ($time_stats as $stat):
|
|
$height = $max_count > 0 ? ($stat['count'] / $max_count) * 150 : 0;
|
|
?>
|
|
<div class="bar" style="height: <?php echo $height; ?>px;">
|
|
<div class="bar-value"><?php echo $stat['count']; ?></div>
|
|
<div class="bar-label"><?php echo substr($stat['reservation_time'], 0, 5); ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- 일별 상세 통계 -->
|
|
<div class="table-container">
|
|
<div class="table-header">일별 예약 현황</div>
|
|
<div class="table-content">
|
|
<?php if (!empty($daily_stats)): ?>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>날짜</th>
|
|
<th>전체</th>
|
|
<th>입금대기</th>
|
|
<th>예약확정</th>
|
|
<th>상담완료</th>
|
|
<th>예약취소</th>
|
|
<th>매출</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($daily_stats as $stat): ?>
|
|
<tr>
|
|
<td><?php echo $stat['reservation_date']; ?></td>
|
|
<td><?php echo number_format($stat['total']); ?></td>
|
|
<td><?php echo number_format($stat['pending']); ?></td>
|
|
<td><?php echo number_format($stat['confirmed']); ?></td>
|
|
<td><?php echo number_format($stat['completed']); ?></td>
|
|
<td><?php echo number_format($stat['cancelled']); ?></td>
|
|
<td><?php echo number_format($stat['revenue']); ?>원</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php else: ?>
|
|
<div class="no-data">
|
|
선택한 기간에 예약 데이터가 없습니다.
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 차트 애니메이션
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const bars = document.querySelectorAll('.bar');
|
|
bars.forEach((bar, index) => {
|
|
setTimeout(() => {
|
|
bar.style.opacity = '0';
|
|
bar.style.transform = 'scaleY(0)';
|
|
bar.style.transformOrigin = 'bottom';
|
|
|
|
setTimeout(() => {
|
|
bar.style.transition = 'all 0.5s ease';
|
|
bar.style.opacity = '1';
|
|
bar.style.transform = 'scaleY(1)';
|
|
}, 100);
|
|
}, index * 100);
|
|
});
|
|
});
|
|
|
|
// 기간 설정 단축키
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const startDateInput = document.querySelector('input[name="start_date"]');
|
|
const endDateInput = document.querySelector('input[name="end_date"]');
|
|
|
|
// 오늘 날짜
|
|
const today = new Date();
|
|
const todayStr = today.toISOString().split('T')[0];
|
|
|
|
// 이번 주 시작일 (월요일)
|
|
const thisWeekStart = new Date(today);
|
|
thisWeekStart.setDate(today.getDate() - today.getDay() + 1);
|
|
const thisWeekStartStr = thisWeekStart.toISOString().split('T')[0];
|
|
|
|
// 이번 달 시작일
|
|
const thisMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
const thisMonthStartStr = thisMonthStart.toISOString().split('T')[0];
|
|
|
|
// 단축키 버튼들 추가
|
|
const filterForm = document.querySelector('.filter-form');
|
|
const shortcutButtons = document.createElement('div');
|
|
shortcutButtons.innerHTML = `
|
|
<button type="button" onclick="setDateRange('${todayStr}', '${todayStr}')" class="btn btn-secondary">오늘</button>
|
|
<button type="button" onclick="setDateRange('${thisWeekStartStr}', '${todayStr}')" class="btn btn-secondary">이번주</button>
|
|
<button type="button" onclick="setDateRange('${thisMonthStartStr}', '${todayStr}')" class="btn btn-secondary">이번달</button>
|
|
`;
|
|
filterForm.appendChild(shortcutButtons);
|
|
|
|
window.setDateRange = function (start, end) {
|
|
startDateInput.value = start;
|
|
endDateInput.value = end;
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<?php
|
|
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
|
?>
|