587 lines
22 KiB
PHP
587 lines
22 KiB
PHP
<?php
|
|
$sub_menu = "800200";
|
|
include_once('./_common.php');
|
|
|
|
auth_check($auth[$sub_menu], 'r');
|
|
|
|
$g5['title'] = '견적 통계 및 리포트';
|
|
|
|
// EstimateManager 로드
|
|
require_once G5_PATH . '/adm/order_manage/classes/EstimateManager.class.php';
|
|
$estimate_manager = new EstimateManager();
|
|
|
|
// 필터 파라미터
|
|
$date_from = isset($_GET['date_from']) ? clean_xss_tags($_GET['date_from']) : date('Y-m-01'); // 이번 달 첫째 날
|
|
$date_to = isset($_GET['date_to']) ? clean_xss_tags($_GET['date_to']) : date('Y-m-d'); // 오늘
|
|
$report_type = isset($_GET['report_type']) ? clean_xss_tags($_GET['report_type']) : 'overview';
|
|
|
|
// CSV 내보내기 처리
|
|
if (isset($_GET['export']) && $_GET['export'] === 'csv') {
|
|
$export_type = clean_xss_tags($_GET['export_type'] ?? 'estimates');
|
|
$csv_data = $estimate_manager->exportStatisticsCSV($export_type, $date_from, $date_to);
|
|
|
|
if ($csv_data) {
|
|
$filename = "order_statistics_{$export_type}_" . date('Y-m-d') . ".csv";
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
|
|
// UTF-8 BOM 추가 (엑셀에서 한글 깨짐 방지)
|
|
echo "\xEF\xBB\xBF";
|
|
echo $csv_data;
|
|
exit;
|
|
} else {
|
|
alert('CSV 내보내기에 실패했습니다.');
|
|
}
|
|
}
|
|
|
|
// 통계 데이터 조회
|
|
$estimate_stats = $estimate_manager->getEstimateStatistics($date_from, $date_to);
|
|
$dealer_performance = $estimate_manager->getDealerPerformance($date_from, $date_to);
|
|
$revenue_stats = $estimate_manager->getRevenueStatistics($date_from, $date_to);
|
|
$expert_visit_stats = $estimate_manager->getExpertVisitStatistics($date_from, $date_to);
|
|
|
|
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
|
?>
|
|
|
|
<div class="local_desc01 local_desc">
|
|
<p>
|
|
견적 요청 현황, 대리점 성과, 매출 분석, 전문가 방문 통계 등 종합적인 비즈니스 인사이트를 제공합니다.<br>
|
|
데이터를 CSV 파일로 내보내어 상세 분석이 가능합니다.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 필터 및 내보내기 영역 -->
|
|
<div class="local_sch01 local_sch">
|
|
<form name="fsearch" method="get">
|
|
<fieldset>
|
|
<legend>기간 설정</legend>
|
|
|
|
<label for="date_from" class="sound_only">시작일</label>
|
|
<input type="date" name="date_from" id="date_from" value="<?php echo $date_from; ?>" class="frm_input">
|
|
~
|
|
<label for="date_to" class="sound_only">종료일</label>
|
|
<input type="date" name="date_to" id="date_to" value="<?php echo $date_to; ?>" class="frm_input">
|
|
|
|
<input type="submit" value="조회" class="btn_submit">
|
|
|
|
<div style="float: right;">
|
|
<select id="export_type" class="frm_input">
|
|
<option value="estimates">견적 통계</option>
|
|
<option value="dealers">대리점 성과</option>
|
|
<option value="revenue">매출 현황</option>
|
|
<option value="expert_visits">전문가 방문</option>
|
|
</select>
|
|
<button type="button" onclick="exportCSV()" class="btn btn_02">CSV 내보내기</button>
|
|
</div>
|
|
</fieldset>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 탭 메뉴 -->
|
|
<div class="tab-menu">
|
|
<button class="tab-btn active" onclick="showTab('overview')">전체 개요</button>
|
|
<button class="tab-btn" onclick="showTab('estimates')">견적 통계</button>
|
|
<button class="tab-btn" onclick="showTab('dealers')">대리점 성과</button>
|
|
<button class="tab-btn" onclick="showTab('revenue')">매출 분석</button>
|
|
<button class="tab-btn" onclick="showTab('expert_visits')">전문가 방문</button>
|
|
</div>
|
|
|
|
<!-- 전체 개요 탭 -->
|
|
<div id="overview-tab" class="tab-content active">
|
|
<div class="stats-grid">
|
|
<!-- 견적 현황 카드 -->
|
|
<div class="stats-card">
|
|
<h3>견적 현황</h3>
|
|
<div class="stats-number">
|
|
<?php echo number_format($estimate_stats['total_statistics']['total_estimates']); ?></div>
|
|
<div class="stats-label">총 견적 요청</div>
|
|
<div class="stats-detail">
|
|
완료: <?php echo number_format($estimate_stats['total_statistics']['completed_estimates']); ?>건 |
|
|
선택: <?php echo number_format($estimate_stats['total_statistics']['selected_estimates']); ?>건 |
|
|
전환율: <?php echo $estimate_stats['total_statistics']['conversion_rate']; ?>%
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 매출 현황 카드 -->
|
|
<div class="stats-card">
|
|
<h3>매출 현황</h3>
|
|
<div class="stats-number">
|
|
<?php echo number_format($revenue_stats['total_revenue']['total_revenue'] ?? 0); ?>원</div>
|
|
<div class="stats-label">총 매출</div>
|
|
<div class="stats-detail">
|
|
주문: <?php echo number_format($revenue_stats['total_revenue']['total_orders'] ?? 0); ?>건 |
|
|
평균: <?php echo number_format($revenue_stats['total_revenue']['avg_order_value'] ?? 0); ?>원
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 대리점 현황 카드 -->
|
|
<div class="stats-card">
|
|
<h3>대리점 현황</h3>
|
|
<div class="stats-number"><?php echo count($dealer_performance); ?></div>
|
|
<div class="stats-label">활성 대리점</div>
|
|
<div class="stats-detail">
|
|
<?php
|
|
$total_bids = array_sum(array_column($dealer_performance, 'total_bids'));
|
|
$selected_bids = array_sum(array_column($dealer_performance, 'selected_bids'));
|
|
$avg_success_rate = $total_bids > 0 ? round(($selected_bids / $total_bids) * 100, 2) : 0;
|
|
?>
|
|
총 입찰: <?php echo number_format($total_bids); ?>건 |
|
|
평균 성공률: <?php echo $avg_success_rate; ?>%
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 전문가 방문 카드 -->
|
|
<div class="stats-card">
|
|
<h3>전문가 방문</h3>
|
|
<div class="stats-number">
|
|
<?php echo number_format($expert_visit_stats['visit_statistics']['total_requests'] ?? 0); ?></div>
|
|
<div class="stats-label">방문 요청</div>
|
|
<div class="stats-detail">
|
|
완료: <?php echo number_format($expert_visit_stats['visit_statistics']['completed_visits'] ?? 0); ?>건 |
|
|
매출: <?php echo number_format($expert_visit_stats['visit_statistics']['total_visit_revenue'] ?? 0); ?>원
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 월별 트렌드 차트 -->
|
|
<div class="chart-container">
|
|
<h3>월별 견적 요청 트렌드</h3>
|
|
<canvas id="monthlyTrendChart" width="800" height="400"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 견적 통계 탭 -->
|
|
<div id="estimates-tab" class="tab-content">
|
|
<div class="tbl_head01 tbl_wrap">
|
|
<table>
|
|
<caption>상태별 견적 분포</caption>
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">상태</th>
|
|
<th scope="col">건수</th>
|
|
<th scope="col">비율</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$total_estimates = $estimate_stats['total_statistics']['total_estimates'];
|
|
foreach ($estimate_stats['status_distribution'] as $status):
|
|
$percentage = $total_estimates > 0 ? round(($status['count'] / $total_estimates) * 100, 2) : 0;
|
|
?>
|
|
<tr>
|
|
<td class="td_left"><?php echo get_text($status['status']); ?></td>
|
|
<td class="td_num"><?php echo number_format($status['count']); ?>건</td>
|
|
<td class="td_num"><?php echo $percentage; ?>%</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<h3>월별 견적 전환율</h3>
|
|
<canvas id="conversionChart" width="800" height="400"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 대리점 성과 탭 -->
|
|
<div id="dealers-tab" class="tab-content">
|
|
<div class="tbl_head01 tbl_wrap">
|
|
<table>
|
|
<caption>대리점별 성과 분석</caption>
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">대리점</th>
|
|
<th scope="col">레벨</th>
|
|
<th scope="col">총 입찰</th>
|
|
<th scope="col">선택된 입찰</th>
|
|
<th scope="col">성공률</th>
|
|
<th scope="col">평균 입찰금액</th>
|
|
<th scope="col">총 매출</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($dealer_performance)): ?>
|
|
<tr>
|
|
<td colspan="7" class="empty_table">대리점 성과 데이터가 없습니다.</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($dealer_performance as $dealer): ?>
|
|
<tr>
|
|
<td class="td_left">
|
|
<strong><?php echo get_text($dealer['dealer_name']); ?></strong><br>
|
|
<small><?php echo $dealer['dealer_id']; ?></small>
|
|
</td>
|
|
<td class="td_num">레벨 <?php echo $dealer['dealer_level']; ?></td>
|
|
<td class="td_num"><?php echo number_format($dealer['total_bids']); ?>건</td>
|
|
<td class="td_num"><?php echo number_format($dealer['selected_bids']); ?>건</td>
|
|
<td class="td_num">
|
|
<span
|
|
class="<?php echo $dealer['success_rate'] >= 30 ? 'text-success' : ($dealer['success_rate'] >= 15 ? 'text-warning' : 'text-danger'); ?>">
|
|
<?php echo $dealer['success_rate']; ?>%
|
|
</span>
|
|
</td>
|
|
<td class="td_num"><?php echo number_format($dealer['avg_bid_amount']); ?>원</td>
|
|
<td class="td_num"><?php echo number_format($dealer['total_revenue']); ?>원</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 매출 분석 탭 -->
|
|
<div id="revenue-tab" class="tab-content">
|
|
<div class="stats-grid">
|
|
<div class="stats-card">
|
|
<h4>총 주문</h4>
|
|
<div class="stats-number">
|
|
<?php echo number_format($revenue_stats['total_revenue']['total_orders'] ?? 0); ?>건</div>
|
|
</div>
|
|
<div class="stats-card">
|
|
<h4>총 매출</h4>
|
|
<div class="stats-number">
|
|
<?php echo number_format($revenue_stats['total_revenue']['total_revenue'] ?? 0); ?>원</div>
|
|
</div>
|
|
<div class="stats-card">
|
|
<h4>평균 주문금액</h4>
|
|
<div class="stats-number">
|
|
<?php echo number_format($revenue_stats['total_revenue']['avg_order_value'] ?? 0); ?>원</div>
|
|
</div>
|
|
<div class="stats-card">
|
|
<h4>결제 완료율</h4>
|
|
<?php
|
|
$payment_rate = ($revenue_stats['total_revenue']['total_orders'] ?? 0) > 0
|
|
? round((($revenue_stats['total_revenue']['paid_orders'] ?? 0) / $revenue_stats['total_revenue']['total_orders']) * 100, 2)
|
|
: 0;
|
|
?>
|
|
<div class="stats-number"><?php echo $payment_rate; ?>%</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tbl_head01 tbl_wrap">
|
|
<table>
|
|
<caption>결제 단계별 현황</caption>
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">결제 단계</th>
|
|
<th scope="col">건수</th>
|
|
<th scope="col">매출</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($revenue_stats['payment_breakdown'] as $payment): ?>
|
|
<tr>
|
|
<td class="td_left"><?php echo get_text($payment['status']); ?></td>
|
|
<td class="td_num"><?php echo number_format($payment['count']); ?>건</td>
|
|
<td class="td_num"><?php echo number_format($payment['revenue']); ?>원</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<h3>월별 매출 트렌드</h3>
|
|
<canvas id="revenueChart" width="800" height="400"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 전문가 방문 탭 -->
|
|
<div id="expert_visits-tab" class="tab-content">
|
|
<div class="stats-grid">
|
|
<div class="stats-card">
|
|
<h4>총 방문 요청</h4>
|
|
<div class="stats-number">
|
|
<?php echo number_format($expert_visit_stats['visit_statistics']['total_requests'] ?? 0); ?>건</div>
|
|
</div>
|
|
<div class="stats-card">
|
|
<h4>완료된 방문</h4>
|
|
<div class="stats-number">
|
|
<?php echo number_format($expert_visit_stats['visit_statistics']['completed_visits'] ?? 0); ?>건</div>
|
|
</div>
|
|
<div class="stats-card">
|
|
<h4>방문 매출</h4>
|
|
<div class="stats-number">
|
|
<?php echo number_format($expert_visit_stats['visit_statistics']['total_visit_revenue'] ?? 0); ?>원</div>
|
|
</div>
|
|
<div class="stats-card">
|
|
<h4>평균 방문비</h4>
|
|
<div class="stats-number">
|
|
<?php echo number_format($expert_visit_stats['visit_statistics']['avg_visit_fee'] ?? 0); ?>원</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tbl_head01 tbl_wrap">
|
|
<table>
|
|
<caption>전문가별 방문 성과</caption>
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">전문가</th>
|
|
<th scope="col">배정된 방문</th>
|
|
<th scope="col">완료된 방문</th>
|
|
<th scope="col">완료율</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($expert_visit_stats['expert_performance'])): ?>
|
|
<tr>
|
|
<td colspan="4" class="empty_table">전문가 방문 데이터가 없습니다.</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($expert_visit_stats['expert_performance'] as $expert): ?>
|
|
<tr>
|
|
<td class="td_left">
|
|
<strong><?php echo get_text($expert['expert_name']); ?></strong><br>
|
|
<small><?php echo $expert['expert_id']; ?></small>
|
|
</td>
|
|
<td class="td_num"><?php echo number_format($expert['assigned_visits']); ?>건</td>
|
|
<td class="td_num"><?php echo number_format($expert['completed_visits']); ?>건</td>
|
|
<td class="td_num">
|
|
<span
|
|
class="<?php echo $expert['completion_rate'] >= 80 ? 'text-success' : ($expert['completion_rate'] >= 60 ? 'text-warning' : 'text-danger'); ?>">
|
|
<?php echo $expert['completion_rate']; ?>%
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.tab-menu {
|
|
margin: 20px 0;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.tab-btn {
|
|
background: none;
|
|
border: none;
|
|
padding: 10px 20px;
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.tab-btn.active {
|
|
border-bottom-color: #007cba;
|
|
color: #007cba;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
padding: 20px 0;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.stats-card {
|
|
background: #f8f9fa;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
border-left: 4px solid #007cba;
|
|
}
|
|
|
|
.stats-card h3,
|
|
.stats-card h4 {
|
|
margin: 0 0 10px 0;
|
|
color: #333;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.stats-number {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #007cba;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.stats-label {
|
|
color: #666;
|
|
font-size: 12px;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.stats-detail {
|
|
color: #888;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.chart-container {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.chart-container h3 {
|
|
margin: 0 0 20px 0;
|
|
color: #333;
|
|
}
|
|
|
|
.text-success {
|
|
color: #28a745;
|
|
}
|
|
|
|
.text-warning {
|
|
color: #ffc107;
|
|
}
|
|
|
|
.text-danger {
|
|
color: #dc3545;
|
|
}
|
|
</style>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script>
|
|
// 탭 전환
|
|
function showTab(tabName) {
|
|
// 모든 탭 비활성화
|
|
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
|
|
|
// 선택된 탭 활성화
|
|
event.target.classList.add('active');
|
|
document.getElementById(tabName + '-tab').classList.add('active');
|
|
}
|
|
|
|
// CSV 내보내기
|
|
function exportCSV() {
|
|
const exportType = document.getElementById('export_type').value;
|
|
const dateFrom = document.getElementById('date_from').value;
|
|
const dateTo = document.getElementById('date_to').value;
|
|
|
|
const url = `?export=csv&export_type=${exportType}&date_from=${dateFrom}&date_to=${dateTo}`;
|
|
window.location.href = url;
|
|
}
|
|
|
|
// 차트 데이터 준비
|
|
const monthlyTrendData = <?php echo json_encode($estimate_stats['monthly_trend']); ?>;
|
|
const monthlyRevenueData = <?php echo json_encode($revenue_stats['monthly_revenue']); ?>;
|
|
|
|
// 월별 트렌드 차트
|
|
if (document.getElementById('monthlyTrendChart')) {
|
|
const ctx1 = document.getElementById('monthlyTrendChart').getContext('2d');
|
|
new Chart(ctx1, {
|
|
type: 'line',
|
|
data: {
|
|
labels: monthlyTrendData.map(item => item.month),
|
|
datasets: [{
|
|
label: '총 견적수',
|
|
data: monthlyTrendData.map(item => item.total_count),
|
|
borderColor: '#007cba',
|
|
backgroundColor: 'rgba(0, 124, 186, 0.1)',
|
|
tension: 0.4
|
|
}, {
|
|
label: '선택된 견적수',
|
|
data: monthlyTrendData.map(item => item.selected_count),
|
|
borderColor: '#28a745',
|
|
backgroundColor: 'rgba(40, 167, 69, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 전환율 차트
|
|
if (document.getElementById('conversionChart')) {
|
|
const ctx2 = document.getElementById('conversionChart').getContext('2d');
|
|
new Chart(ctx2, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: monthlyTrendData.map(item => item.month),
|
|
datasets: [{
|
|
label: '전환율 (%)',
|
|
data: monthlyTrendData.map(item => item.conversion_rate),
|
|
backgroundColor: 'rgba(255, 193, 7, 0.8)',
|
|
borderColor: '#ffc107',
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
max: 100
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 매출 차트
|
|
if (document.getElementById('revenueChart')) {
|
|
const ctx3 = document.getElementById('revenueChart').getContext('2d');
|
|
new Chart(ctx3, {
|
|
type: 'line',
|
|
data: {
|
|
labels: monthlyRevenueData.map(item => item.month),
|
|
datasets: [{
|
|
label: '월별 매출 (원)',
|
|
data: monthlyRevenueData.map(item => item.revenue),
|
|
borderColor: '#dc3545',
|
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
|
tension: 0.4,
|
|
yAxisID: 'y'
|
|
}, {
|
|
label: '주문 건수',
|
|
data: monthlyRevenueData.map(item => item.order_count),
|
|
borderColor: '#6f42c1',
|
|
backgroundColor: 'rgba(111, 66, 193, 0.1)',
|
|
tension: 0.4,
|
|
yAxisID: 'y1'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'left',
|
|
beginAtZero: true
|
|
},
|
|
y1: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'right',
|
|
beginAtZero: true,
|
|
grid: {
|
|
drawOnChartArea: false,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<?php
|
|
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
|
?>
|