Files
2026-09-16 23:20:08 +07:00

216 lines
9.9 KiB
PHP

<?php
// views/dashboard/executive.php
?>
<div class="mb-6 flex justify-between items-end flex-wrap gap-4">
<div>
<h2 class="text-2xl font-bold text-slate-800 dark:text-white">การวิเคราะห์ระบบ (Executive Dashboard)</h2>
<p class="text-slate-500 dark:text-slate-400">ดูสถิติประสิทธิภาพและตัวชี้วัด (KPI) ของหน่วยงาน</p>
</div>
<div class="flex gap-2">
<select class="glass-input text-sm py-2 px-3" id="periodSelect" onchange="loadExecutiveData()">
<option value="today">วันนี้</option>
<option value="week">สัปดาห์นี้</option>
<option value="month">เดือนนี้</option>
</select>
<button class="btn-primary-glass py-2 px-4 text-sm font-medium flex items-center">
<i class="fa-solid fa-file-export me-2"></i> Export CSV
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="glass-card p-6 border-t-4 border-t-emerald-500">
<h6 class="text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">SLA Compliance (< 15 นาที)</h6>
<h2 class="text-4xl font-bold text-slate-800 dark:text-white" id="slaPercent">0%</h2>
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2 mt-4 overflow-hidden">
<div class="bg-emerald-500 h-2 rounded-full" id="slaBar" style="width: 0%"></div>
</div>
</div>
<div class="glass-card p-6 border-t-4 border-t-blue-500">
<h6 class="text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">เวลารอคอยเฉลี่ย (Wait Time)</h6>
<h2 class="text-4xl font-bold text-slate-800 dark:text-white"><span id="avgWaitTime">0</span> <span class="text-xl text-slate-500">นาที</span></h2>
<p class="text-xs text-red-500 mt-2"><i class="fa-solid fa-arrow-trend-up"></i> ช้าลงจากเมื่อวาน 2 นาที</p>
</div>
<div class="glass-card p-6 border-t-4 border-t-purple-500">
<h6 class="text-sm font-medium text-slate-500 dark:text-slate-400 mb-1">เวลาให้บริการเฉลี่ย (Service Time)</h6>
<h2 class="text-4xl font-bold text-slate-800 dark:text-white"><span id="avgServiceTime">0</span> <span class="text-xl text-slate-500">นาที</span></h2>
<p class="text-xs text-green-500 mt-2"><i class="fa-solid fa-arrow-trend-down"></i> เร็วขึ้นจากเมื่อวาน 1 นาที</p>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="glass-card p-6">
<h5 class="font-bold text-slate-800 dark:text-white mb-4">แนวโน้มงาน 7 วันย้อนหลัง (Trend)</h5>
<div class="relative h-72">
<canvas id="trendChart"></canvas>
</div>
</div>
<div class="glass-card p-6">
<h5 class="font-bold text-slate-800 dark:text-white mb-4">ภาระงานแยกตามแผนก (Workload by Dept)</h5>
<div class="relative h-72">
<canvas id="deptChart"></canvas>
</div>
</div>
</div>
<div class="glass-card overflow-hidden mb-6">
<div class="p-6 border-b border-slate-200 dark:border-slate-700">
<h5 class="font-bold text-slate-800 dark:text-white">สถิติรายบุคคล (Staff Performance)</h5>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-100/50 dark:bg-slate-800/50 text-slate-500 dark:text-slate-400 text-xs uppercase tracking-wider">
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700">พนักงาน</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 text-center">รับงานทั้งหมด</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 text-center">สำเร็จ</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 text-center">ยกเลิก/ปฏิเสธ</th>
<th class="p-4 font-medium border-b border-slate-200 dark:border-slate-700 text-center">เวลาบริการเฉลี่ย</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800 text-sm" id="staffPerformanceTable">
<tr>
<td colspan="5" class="p-8 text-center text-slate-500"><i class="fa-solid fa-spinner fa-spin me-2"></i>กำลังโหลด...</td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
let trendChartInstance = null;
let deptChartInstance = null;
document.addEventListener('DOMContentLoaded', function() {
loadExecutiveData();
});
function loadExecutiveData() {
const period = document.getElementById('periodSelect').value;
fetch(`api/executive.php?period=${period}`)
.then(response => response.json())
.then(data => {
if (data.success) {
// Update KPI Cards
const kpi = data.kpi;
document.getElementById('slaPercent').textContent = kpi.sla_percent + '%';
document.getElementById('slaBar').style.width = kpi.sla_percent + '%';
document.getElementById('avgWaitTime').textContent = kpi.avg_wait_time;
document.getElementById('avgServiceTime').textContent = kpi.avg_service_time;
// Update Charts
initCharts(data.charts);
// Update Staff Performance Table
renderStaffPerformance(data.staff_performance);
}
})
.catch(error => console.error('Error fetching dashboard data:', error));
}
function renderStaffPerformance(staffData) {
const tbody = document.getElementById('staffPerformanceTable');
tbody.innerHTML = '';
if (!staffData || staffData.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="p-8 text-center text-slate-500">ไม่มีข้อมูลในช่วงเวลานี้</td></tr>';
return;
}
staffData.forEach(staff => {
const total = staff.total_jobs || 0;
const completed = staff.completed_jobs || 0;
const cancelled = staff.cancelled_jobs || 0;
const avgTime = staff.avg_service_time ? Math.round(staff.avg_service_time) : 0;
tbody.innerHTML += `
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
<td class="p-4 font-medium text-slate-800 dark:text-slate-200">
<div class="flex items-center gap-3">
<img src="api/avatar.php?name=${encodeURIComponent(staff.full_name)}" class="w-8 h-8 rounded-full border border-slate-200 dark:border-slate-700">
${staff.full_name}
</div>
</td>
<td class="p-4 text-center font-bold text-blue-600 dark:text-blue-400">${total}</td>
<td class="p-4 text-center font-bold text-emerald-600 dark:text-emerald-400">${completed}</td>
<td class="p-4 text-center font-bold text-red-500 dark:text-red-400">${cancelled}</td>
<td class="p-4 text-center font-bold text-purple-600 dark:text-purple-400">${avgTime} นาที</td>
</tr>
`;
});
}
function initCharts(chartData) {
// Determine text colors based on theme
const isDark = document.documentElement.classList.contains('dark');
const textColor = isDark ? '#cbd5e1' : '#475569';
const gridColor = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';
// Trend Chart
const ctxTrend = document.getElementById('trendChart').getContext('2d');
if (trendChartInstance) {
trendChartInstance.destroy();
}
let gradient = ctxTrend.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, 'rgba(16, 185, 129, 0.5)'); // emerald-500
gradient.addColorStop(1, 'rgba(16, 185, 129, 0)');
trendChartInstance = new Chart(ctxTrend, {
type: 'line',
data: {
labels: chartData.trend.labels,
datasets: [{
label: 'จำนวนงาน',
data: chartData.trend.data,
borderColor: '#10b981',
backgroundColor: gradient,
borderWidth: 2,
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, grid: { color: gridColor }, ticks: { color: textColor } },
x: { grid: { display: false }, ticks: { color: textColor } }
}
}
});
// Department Chart
const ctxDept = document.getElementById('deptChart').getContext('2d');
if (deptChartInstance) {
deptChartInstance.destroy();
}
deptChartInstance = new Chart(ctxDept, {
type: 'bar',
data: {
labels: chartData.departments.labels,
datasets: [{
label: 'จำนวนงาน (Job)',
data: chartData.departments.data,
backgroundColor: '#3b82f6',
borderRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, grid: { color: gridColor }, ticks: { color: textColor } },
x: { grid: { display: false }, ticks: { color: textColor } }
}
}
});
}
</script>