Files
dnssash/adm/consultant_manage/statistics.php
T
2026-06-11 18:47:38 +09:00

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');
?>