578 lines
17 KiB
PHP
578 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* 상담 예약 관리 시스템 대시보드
|
|
*/
|
|
$sub_menu = '850100';
|
|
include_once('./_common.php');
|
|
|
|
// 관리자 권한 확인
|
|
if (!$is_admin) {
|
|
alert('관리자만 접근할 수 있습니다.');
|
|
}
|
|
|
|
// 설치 확인
|
|
if (!is_consultant_installed()) {
|
|
alert('상담 예약 시스템이 설치되지 않았습니다.', 'install.php');
|
|
}
|
|
|
|
$g5['title'] = '상담 예약 대시보드';
|
|
|
|
// 오늘 날짜 기준 통계
|
|
$today = date('Y-m-d');
|
|
$this_week_start = date('Y-m-d', strtotime('monday this week'));
|
|
$this_month_start = date('Y-m-01');
|
|
|
|
// 오늘 예약 현황
|
|
$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
|
|
FROM consultant_reservations
|
|
WHERE reservation_date = '{$today}' AND is_deleted = 0";
|
|
$today_stats = sql_fetch($sql);
|
|
|
|
// 이번 주 예약 현황
|
|
$sql = "SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as revenue
|
|
FROM consultant_reservations
|
|
WHERE reservation_date >= '{$this_week_start}'
|
|
AND reservation_date <= '{$today}'
|
|
AND is_deleted = 0";
|
|
$week_stats = sql_fetch($sql);
|
|
|
|
// 이번 달 예약 현황
|
|
$sql = "SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as revenue
|
|
FROM consultant_reservations
|
|
WHERE reservation_date >= '{$this_month_start}'
|
|
AND is_deleted = 0";
|
|
$month_stats = sql_fetch($sql);
|
|
|
|
// 최근 예약 목록 (5개)
|
|
$sql = "SELECT * FROM consultant_reservations
|
|
WHERE is_deleted = 0
|
|
ORDER BY created_at DESC
|
|
LIMIT 5";
|
|
$recent_reservations = [];
|
|
$result = sql_query($sql);
|
|
while ($row = sql_fetch_array($result)) {
|
|
$recent_reservations[] = $row;
|
|
}
|
|
|
|
// 오늘 예약 목록
|
|
$sql = "SELECT * FROM consultant_reservations
|
|
WHERE reservation_date = '{$today}'
|
|
AND is_deleted = 0
|
|
ORDER BY reservation_time";
|
|
$today_reservations = [];
|
|
$result = sql_query($sql);
|
|
while ($row = sql_fetch_array($result)) {
|
|
$today_reservations[] = $row;
|
|
}
|
|
|
|
// 입금 대기 예약 수
|
|
$sql = "SELECT COUNT(*) as count FROM consultant_reservations
|
|
WHERE status = 'payment_pending' AND is_deleted = 0";
|
|
$pending_count = sql_fetch($sql)['count'];
|
|
|
|
// 시간대별 예약 현황 (이번 주)
|
|
$sql = "SELECT
|
|
reservation_time,
|
|
COUNT(*) as count
|
|
FROM consultant_reservations
|
|
WHERE reservation_date >= '{$this_week_start}'
|
|
AND reservation_date <= '{$today}'
|
|
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;
|
|
}
|
|
|
|
include_once(G5_ADMIN_PATH . '/admin.head.php');
|
|
?>
|
|
|
|
<style>
|
|
.dashboard-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
.dashboard-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 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;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.stat-sublabel {
|
|
color: #999;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.stat-card.today .stat-number {
|
|
color: #007bff;
|
|
}
|
|
|
|
.stat-card.week .stat-number {
|
|
color: #28a745;
|
|
}
|
|
|
|
.stat-card.month .stat-number {
|
|
color: #17a2b8;
|
|
}
|
|
|
|
.stat-card.pending .stat-number {
|
|
color: #ffc107;
|
|
}
|
|
|
|
.content-grid {
|
|
display: grid;
|
|
grid-template-columns: 2fr 1fr;
|
|
gap: 20px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.content-card {
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.card-header {
|
|
background: #fff;
|
|
padding: 15px 20px;
|
|
border-bottom: 1px solid #ddd;
|
|
font-weight: bold;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.card-content {
|
|
padding: 20px;
|
|
}
|
|
|
|
.reservation-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.reservation-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.reservation-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.reservation-name {
|
|
font-weight: bold;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.reservation-details {
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
.reservation-status {
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 11px;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
min-width: 60px;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.quick-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.btn-success {
|
|
background: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.btn-warning {
|
|
background: #ffc107;
|
|
color: #212529;
|
|
}
|
|
|
|
.btn-info {
|
|
background: #17a2b8;
|
|
color: white;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
color: #666;
|
|
}
|
|
|
|
.chart-container {
|
|
height: 200px;
|
|
display: flex;
|
|
align-items: end;
|
|
justify-content: space-around;
|
|
padding: 20px 0;
|
|
border-top: 1px solid #eee;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.chart-bar {
|
|
background: #007bff;
|
|
width: 30px;
|
|
border-radius: 2px 2px 0 0;
|
|
position: relative;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.chart-bar:hover {
|
|
background: #0056b3;
|
|
}
|
|
|
|
.chart-label {
|
|
position: absolute;
|
|
bottom: -25px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
font-size: 11px;
|
|
color: #666;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.chart-value {
|
|
position: absolute;
|
|
top: -20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
font-size: 10px;
|
|
color: #333;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.alert {
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid transparent;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.alert-warning {
|
|
color: #856404;
|
|
background-color: #fff3cd;
|
|
border-color: #ffeaa7;
|
|
}
|
|
|
|
.alert-info {
|
|
color: #0c5460;
|
|
background-color: #d1ecf1;
|
|
border-color: #bee5eb;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.content-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.dashboard-header {
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.quick-actions {
|
|
justify-content: center;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="dashboard-container">
|
|
<div class="dashboard-header">
|
|
<div>
|
|
<h2><?php echo $g5['title']; ?></h2>
|
|
<p>상담 예약 현황을 한눈에 확인하세요</p>
|
|
</div>
|
|
<div class="quick-actions">
|
|
<a href="reservations.php" class="btn btn-primary">예약 관리</a>
|
|
<a href="schedule_generate.php" class="btn btn-success">일정 설정</a>
|
|
<a href="statistics.php" class="btn btn-info">통계 보기</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 통계 카드 -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card today">
|
|
<div class="stat-number"><?php echo number_format($today_stats['total']); ?></div>
|
|
<div class="stat-label">오늘 예약</div>
|
|
<div class="stat-sublabel">
|
|
확정 <?php echo $today_stats['confirmed']; ?>건 |
|
|
대기 <?php echo $today_stats['pending']; ?>건
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card week">
|
|
<div class="stat-number"><?php echo number_format($week_stats['total']); ?></div>
|
|
<div class="stat-label">이번 주 예약</div>
|
|
<div class="stat-sublabel">
|
|
매출 <?php echo number_format($week_stats['revenue']); ?>원
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card month">
|
|
<div class="stat-number"><?php echo number_format($month_stats['total']); ?></div>
|
|
<div class="stat-label">이번 달 예약</div>
|
|
<div class="stat-sublabel">
|
|
매출 <?php echo number_format($month_stats['revenue']); ?>원
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card pending">
|
|
<div class="stat-number"><?php echo number_format($pending_count); ?></div>
|
|
<div class="stat-label">입금 대기</div>
|
|
<div class="stat-sublabel">
|
|
<?php if ($pending_count > 0): ?>
|
|
<a href="reservations.php?status=payment_pending" style="color: #856404;">확인 필요</a>
|
|
<?php else: ?>
|
|
모두 처리됨
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($pending_count > 0): ?>
|
|
<div class="alert alert-warning">
|
|
<strong>알림:</strong> 입금 대기 중인 예약이 <?php echo $pending_count; ?>건 있습니다.
|
|
<a href="reservations.php?status=payment_pending">지금 확인하기</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- 메인 콘텐츠 -->
|
|
<div class="content-grid">
|
|
<!-- 오늘 예약 현황 -->
|
|
<div class="content-card">
|
|
<div class="card-header">
|
|
<span>오늘 예약 현황 (<?php echo date('Y-m-d'); ?>)</span>
|
|
<a href="reservations.php?date=<?php echo $today; ?>" class="btn btn-sm btn-primary">전체 보기</a>
|
|
</div>
|
|
<div class="card-content">
|
|
<?php if (empty($today_reservations)): ?>
|
|
<div class="empty-state">
|
|
<p>오늘 예약된 상담이 없습니다.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<?php foreach ($today_reservations as $reservation): ?>
|
|
<div class="reservation-item">
|
|
<div class="reservation-info">
|
|
<div class="reservation-name">
|
|
<?php echo htmlspecialchars($reservation['customer_name']); ?>
|
|
</div>
|
|
<div class="reservation-details">
|
|
<?php echo $reservation['reservation_time']; ?> |
|
|
<?php echo htmlspecialchars($reservation['customer_phone']); ?> |
|
|
<?php echo number_format($reservation['payment_amount']); ?>원
|
|
</div>
|
|
</div>
|
|
<div class="reservation-status status-<?php echo $reservation['status']; ?>">
|
|
<?php
|
|
$status_labels = [
|
|
'payment_pending' => '입금대기',
|
|
'reserved' => '예약확정',
|
|
'completed' => '상담완료',
|
|
'cancelled' => '예약취소'
|
|
];
|
|
echo $status_labels[$reservation['status']] ?? $reservation['status'];
|
|
?>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 최근 예약 -->
|
|
<div class="content-card">
|
|
<div class="card-header">
|
|
<span>최근 예약</span>
|
|
<a href="reservations.php" class="btn btn-sm btn-primary">전체 보기</a>
|
|
</div>
|
|
<div class="card-content">
|
|
<?php if (empty($recent_reservations)): ?>
|
|
<div class="empty-state">
|
|
<p>최근 예약이 없습니다.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<?php foreach ($recent_reservations as $reservation): ?>
|
|
<div class="reservation-item">
|
|
<div class="reservation-info">
|
|
<div class="reservation-name">
|
|
<?php echo htmlspecialchars($reservation['customer_name']); ?>
|
|
</div>
|
|
<div class="reservation-details">
|
|
<?php echo $reservation['reservation_date']; ?>
|
|
<?php echo $reservation['reservation_time']; ?><br>
|
|
<?php echo date('m-d H:i', strtotime($reservation['created_at'])); ?> 신청
|
|
</div>
|
|
</div>
|
|
<div class="reservation-status status-<?php echo $reservation['status']; ?>">
|
|
<?php
|
|
$status_labels = [
|
|
'payment_pending' => '입금대기',
|
|
'reserved' => '예약확정',
|
|
'completed' => '상담완료',
|
|
'cancelled' => '예약취소'
|
|
];
|
|
echo $status_labels[$reservation['status']] ?? $reservation['status'];
|
|
?>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 시간대별 예약 현황 -->
|
|
<?php if (!empty($time_stats)): ?>
|
|
<div class="content-card">
|
|
<div class="card-header">
|
|
<span>시간대별 예약 현황 (이번 주)</span>
|
|
</div>
|
|
<div class="card-content">
|
|
<div class="chart-container">
|
|
<?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="chart-bar" style="height: <?php echo $height; ?>px;">
|
|
<div class="chart-value"><?php echo $stat['count']; ?></div>
|
|
<div class="chart-label"><?php echo substr($stat['reservation_time'], 0, 5); ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- 시스템 정보 -->
|
|
<div class="alert alert-info">
|
|
<strong>시스템 정보:</strong>
|
|
상담 예약 시스템 v<?php echo G5_CONSULTANT_VERSION; ?> |
|
|
마지막 업데이트: <?php echo date('Y-m-d H:i'); ?> |
|
|
<a href="settings.php">시스템 설정</a>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 실시간 업데이트 (5분마다)
|
|
setInterval(function () {
|
|
location.reload();
|
|
}, 300000);
|
|
|
|
// 차트 애니메이션
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const bars = document.querySelectorAll('.chart-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);
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php
|
|
include_once(G5_ADMIN_PATH . '/admin.tail.php');
|
|
?>
|