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