Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,339 @@
<?php
// views/dashboard/index.php
?>
<div class="mb-6">
<h2 class="text-2xl font-bold text-slate-800 dark:text-white">ภาพรวมงานเปลวันนี้</h2>
<p class="text-slate-500 dark:text-slate-400">สถานะแบบ Real-time ข้อมูลอัปเดตตลอดเวลา</p>
</div>
<!-- Stats Row -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<div class="glass-card p-6 border-l-4 border-l-blue-500 hover:scale-[1.02] transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">งานทั้งหมด (วันนี้)</p>
<h3 class="text-3xl font-bold text-slate-800 dark:text-white mt-1" id="stat-total">0</h3>
</div>
<div class="p-3 bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400 rounded-lg">
<i class="fa-solid fa-list-check fa-lg"></i>
</div>
</div>
</div>
<div class="glass-card p-6 border-l-4 border-l-yellow-500 hover:scale-[1.02] transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">รอดำเนินการ</p>
<h3 class="text-3xl font-bold text-slate-800 dark:text-white mt-1" id="stat-pending">0</h3>
</div>
<div class="p-3 bg-yellow-100 dark:bg-yellow-900/50 text-yellow-600 dark:text-yellow-400 rounded-lg">
<i class="fa-solid fa-clock fa-lg"></i>
</div>
</div>
</div>
<div class="glass-card p-6 border-l-4 border-l-emerald-500 hover:scale-[1.02] transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">กำลังดำเนินการ</p>
<h3 class="text-3xl font-bold text-slate-800 dark:text-white mt-1" id="stat-progress">0</h3>
</div>
<div class="p-3 bg-emerald-100 dark:bg-emerald-900/50 text-emerald-600 dark:text-emerald-400 rounded-lg">
<i class="fa-solid fa-person-walking fa-lg"></i>
</div>
</div>
</div>
<div class="glass-card p-6 border-l-4 border-l-green-500 hover:scale-[1.02] transition-transform">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-slate-500 dark:text-slate-400">เสร็จสิ้น</p>
<h3 class="text-3xl font-bold text-slate-800 dark:text-white mt-1" id="stat-completed">0</h3>
</div>
<div class="p-3 bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400 rounded-lg">
<i class="fa-solid fa-check-double fa-lg"></i>
</div>
</div>
</div>
</div>
<!-- Quick Actions & Table -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<div class="glass-card overflow-hidden">
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-700 bg-white/50 dark:bg-slate-800/50 flex justify-between items-center">
<h5 class="font-bold text-slate-800 dark:text-white m-0">งานล่าสุด</h5>
<button class="text-emerald-600 hover:text-emerald-700 dark:text-emerald-400 text-sm font-medium" onclick="loadDashboardData()"><i class="fa-solid fa-rotate-right"></i> รีเฟรช</button>
</div>
<div class="p-6 overflow-x-auto">
<table class="w-full text-left border-collapse" id="jobsTable">
<thead>
<tr class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider border-b border-slate-200 dark:border-slate-700">
<th class="pb-3 px-4">เลขที่งาน</th>
<th class="pb-3 px-4">เวลาขอ</th>
<th class="pb-3 px-4">จุดรับ -> จุดส่ง</th>
<th class="pb-3 px-4">อุปกรณ์</th>
<th class="pb-3 px-4 text-center">สถานะ</th>
</tr>
</thead>
<tbody class="text-sm text-slate-700 dark:text-slate-300 divide-y divide-slate-200 dark:divide-slate-700" id="jobsTableBody">
<tr><td colspan="5" class="text-center py-4">กำลังโหลดข้อมูล...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="lg:col-span-1">
<div class="glass-card mb-6">
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-700 bg-white/50 dark:bg-slate-800/50">
<h5 class="font-bold text-slate-800 dark:text-white m-0">ทางลัด (Quick Actions)</h5>
</div>
<div class="p-6 flex flex-col gap-3">
<a href="?page=request" class="btn-primary-glass py-3 px-4 text-center font-medium w-full block">
<i class="fa-solid fa-plus-circle me-2"></i> ร้องของานเปลใหม่
</a>
<?php if ($_SESSION['role'] !== 'ward'): ?>
<a href="?page=staff" class="btn-secondary-glass py-3 px-4 text-center font-medium w-full block">
<i class="fa-solid fa-list-check me-2"></i> ดูงานที่ต้องรับผิดชอบ
</a>
<?php endif; ?>
</div>
</div>
<div class="glass-card">
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-700 bg-white/50 dark:bg-slate-800/50">
<h5 class="font-bold text-slate-800 dark:text-white m-0">เจ้าหน้าที่ศูนย์เปล (Online)</h5>
</div>
<div class="p-4 max-h-64 overflow-y-auto">
<ul class="space-y-3" id="staffOnlineList">
<li class="text-center text-sm text-slate-500 py-2">กำลังโหลดข้อมูล...</li>
</ul>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
loadDashboardData();
setInterval(loadDashboardData, 30000); // 30s auto refresh
});
function loadDashboardData() {
fetch('api/dashboard.php')
.then(response => response.json())
.then(res => {
if (res.status === 'success') {
const data = res.data;
// Update Stats
document.getElementById('stat-total').textContent = data.stats.total;
document.getElementById('stat-pending').textContent = data.stats.pending;
document.getElementById('stat-progress').textContent = data.stats.in_progress;
document.getElementById('stat-completed').textContent = data.stats.completed;
// Update Jobs Table
const tbody = document.getElementById('jobsTableBody');
if (data.jobs.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4">ไม่มีข้อมูลงานในวันนี้</td></tr>';
} else {
let html = '';
data.jobs.forEach(job => {
let statusBadge = '';
if (job.status === 'pending') {
statusBadge = '<div class="flex flex-col sm:flex-row gap-2 justify-center items-center"><span class="bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-500 px-3 py-1 rounded-full text-xs font-medium border border-yellow-200 dark:border-yellow-800 whitespace-nowrap">รอดำเนินการ</span>';
// Add Accept button for pending jobs
statusBadge += `<button onclick="acceptJob('${job.job_number}')" class="text-xs bg-emerald-500 hover:bg-emerald-600 text-white px-3 py-1 rounded-full transition-colors shadow-sm whitespace-nowrap flex items-center gap-1"><i class="fa-solid fa-hand-holding-hand"></i> รับงาน</button></div>`;
} else if (job.status === 'assigned' || job.status === 'in_progress' || job.status === 'waiting_for_return') {
statusBadge = '<span class="bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400 px-3 py-1 rounded-full text-xs font-medium border border-blue-200 dark:border-blue-800 whitespace-nowrap">กำลังดำเนินการ</span>';
} else if (job.status === 'completed') {
statusBadge = '<div class="flex flex-col sm:flex-row gap-2 justify-center items-center"><span class="bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-500 px-3 py-1 rounded-full text-xs font-medium border border-green-200 dark:border-green-800 whitespace-nowrap">เสร็จสิ้น</span>';
if (!job.rating) {
statusBadge += `<button onclick="openRatingModal(${job.id}, '${job.job_number}')" class="text-xs bg-yellow-400 hover:bg-yellow-500 text-yellow-900 px-3 py-1 rounded-full transition-colors shadow-sm whitespace-nowrap flex items-center gap-1"><i class="fa-solid fa-star"></i> ให้คะแนน</button></div>`;
} else {
statusBadge += `<span class="text-xs text-yellow-500 bg-yellow-50 dark:bg-yellow-900/20 px-2 py-1 rounded-full whitespace-nowrap"><i class="fa-solid fa-star"></i> ${job.rating}</span></div>`;
}
} else {
statusBadge = '<span class="bg-slate-100 text-slate-800 dark:bg-slate-700 dark:text-slate-300 px-3 py-1 rounded-full text-xs font-medium border border-slate-200 dark:border-slate-600 whitespace-nowrap">ยกเลิก</span>';
}
// Format equipment
let equipmentBadge = '';
if (job.equipment_type === 'wheelchair') equipmentBadge = 'รถนั่ง';
else if (job.equipment_type === 'stretcher') equipmentBadge = 'รถนอน';
else if (job.equipment_type === 'bed') equipmentBadge = 'เตียง';
else equipmentBadge = 'เดินไปเอง';
html += `
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors border-b border-slate-100 dark:border-slate-800 last:border-0">
<td class="py-3 px-4 font-medium text-emerald-600 dark:text-emerald-400 whitespace-nowrap">${job.job_number}</td>
<td class="py-3 px-4 whitespace-nowrap text-slate-500">${new Date(job.created_at).toLocaleTimeString('th-TH', {hour: '2-digit', minute:'2-digit'})} น.</td>
<td class="py-3 px-4 text-sm"><div class="flex items-center gap-2"><span class="truncate max-w-[120px] inline-block" title="${job.from_dept}">${job.from_dept}</span> <i class="fa-solid fa-arrow-right text-slate-300 text-[10px]"></i> <span class="truncate max-w-[120px] inline-block font-medium" title="${job.to_dept}">${job.to_dept}</span></div></td>
<td class="py-3 px-4 whitespace-nowrap"><span class="bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-300 px-2 py-1 rounded text-xs font-medium">${equipmentBadge}</span></td>
<td class="py-3 px-4 text-center">${statusBadge}</td>
</tr>
`;
});
tbody.innerHTML = html;
}
// Update Staff Online List
const staffList = document.getElementById('staffOnlineList');
if (data.staff.length === 0) {
staffList.innerHTML = '<li class="text-center text-sm text-slate-500 py-2">ไม่มีเจ้าหน้าที่ออนไลน์</li>';
} else {
let staffHtml = '';
data.staff.forEach(user => {
let statusColor = user.status === 'online' ? 'bg-green-500' : 'bg-yellow-500';
let statusText = user.status === 'online' ? 'ว่าง' : 'ติดงาน';
staffHtml += `
<li class="flex items-center justify-between p-2 hover:bg-slate-50 dark:hover:bg-slate-800/50 rounded-lg transition-colors">
<div class="flex items-center gap-3">
<div class="relative">
<img src="api/avatar.php?username=${encodeURIComponent(user.username)}&name=${encodeURIComponent(user.full_name || user.username)}" class="w-8 h-8 rounded-full object-cover shadow-sm">
<span class="absolute bottom-0 right-0 w-2.5 h-2.5 ${statusColor} border-2 border-white dark:border-slate-900 rounded-full"></span>
</div>
<span class="text-sm font-medium">${user.username}</span>
</div>
<span class="text-xs text-slate-500 dark:text-slate-400">${statusText}</span>
</li>
`;
});
staffList.innerHTML = staffHtml;
}
} else {
console.error('Error fetching dashboard data:', res.message);
}
})
.catch(err => {
console.error('Fetch error:', err);
});
}
// Rating Modal Functions
function openRatingModal(jobId, jobNumber) {
document.getElementById('rating_job_id').value = jobId;
document.getElementById('ratingJobNumber').textContent = jobNumber;
// Reset stars
document.querySelectorAll('.rating-star').forEach(star => {
star.classList.remove('text-yellow-400', 'fas');
star.classList.add('text-slate-300', 'far');
});
document.getElementById('rating_value').value = '0';
document.getElementById('rating_feedback').value = '';
document.getElementById('ratingModal').classList.remove('hidden');
}
function closeRatingModal() {
document.getElementById('ratingModal').classList.add('hidden');
}
function setRating(rating) {
document.getElementById('rating_value').value = rating;
document.querySelectorAll('.rating-star').forEach((star, index) => {
if (index < rating) {
star.classList.remove('text-slate-300', 'far');
star.classList.add('text-yellow-400', 'fas');
} else {
star.classList.remove('text-yellow-400', 'fas');
star.classList.add('text-slate-300', 'far');
}
});
}
function submitRating() {
const jobId = document.getElementById('rating_job_id').value;
const rating = document.getElementById('rating_value').value;
const feedback = document.getElementById('rating_feedback').value;
if (rating == '0') {
Swal.fire('กรุณาให้คะแนน', 'โปรดเลือกระดับความพึงพอใจ', 'warning');
return;
}
fetch('api/feedback.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `action=rate_job&job_id=${jobId}&rating=${rating}&feedback=${encodeURIComponent(feedback)}&csrf_token=<?= $_SESSION['csrf_token'] ?? '' ?>`
})
.then(res => res.json())
.then(res => {
if (res.status === 'success') {
Swal.fire('ขอบคุณ!', 'บันทึกคะแนนความพึงพอใจแล้ว', 'success');
closeRatingModal();
loadDashboardData();
} else {
Swal.fire('ข้อผิดพลาด', res.message, 'error');
}
})
.catch(err => {
Swal.fire('ข้อผิดพลาด', 'เกิดการเชื่อมต่อล้มเหลว', 'error');
});
}
function acceptJob(jobNumber) {
Swal.fire({
title: 'รับงานนี้?',
text: "คุณต้องการรับงานหมายเลข " + jobNumber + " ใช่หรือไม่?",
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#10b981',
cancelButtonColor: '#ef4444',
confirmButtonText: 'ใช่, รับงาน',
cancelButtonText: 'ยกเลิก'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: 'api/jobs.php',
type: 'POST',
data: {
action: 'assign_job',
job_id: jobNumber
},
success: function(response) {
if(response.status === 'success') {
Swal.fire('สำเร็จ', response.message, 'success').then(() => {
window.location.href = '?page=staff';
});
} else {
Swal.fire('ผิดพลาด', response.message, 'error');
}
},
error: function() {
Swal.fire('ผิดพลาด', 'เกิดข้อผิดพลาดในการเชื่อมต่อ', 'error');
}
});
}
});
}
</script>
<!-- Rating Modal -->
<div id="ratingModal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/50 backdrop-blur-sm">
<div class="glass-card w-full max-w-md p-6 animate-fade-in-up">
<h3 class="text-xl font-bold text-slate-800 dark:text-white mb-4">ประเมินความพึงพอใจ</h3>
<p class="text-sm text-slate-500 mb-4">หมายเลขงาน: <span id="ratingJobNumber" class="font-bold"></span></p>
<input type="hidden" id="rating_job_id" value="">
<input type="hidden" id="rating_value" value="0">
<div class="flex justify-center gap-2 mb-6">
<i class="rating-star far fa-star text-4xl text-slate-300 cursor-pointer hover:scale-110 transition-transform" onclick="setRating(1)"></i>
<i class="rating-star far fa-star text-4xl text-slate-300 cursor-pointer hover:scale-110 transition-transform" onclick="setRating(2)"></i>
<i class="rating-star far fa-star text-4xl text-slate-300 cursor-pointer hover:scale-110 transition-transform" onclick="setRating(3)"></i>
<i class="rating-star far fa-star text-4xl text-slate-300 cursor-pointer hover:scale-110 transition-transform" onclick="setRating(4)"></i>
<i class="rating-star far fa-star text-4xl text-slate-300 cursor-pointer hover:scale-110 transition-transform" onclick="setRating(5)"></i>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">ข้อเสนอแนะเพิ่มเติม (ถ้ามี)</label>
<textarea id="rating_feedback" class="glass-input w-full px-4 py-2" rows="3" placeholder="ระบุข้อเสนอแนะ..."></textarea>
</div>
<div class="flex justify-end gap-3">
<button type="button" onclick="closeRatingModal()" class="btn-secondary-glass px-4 py-2">ยกเลิก</button>
<button type="button" onclick="submitRating()" class="btn-primary-glass px-6 py-2">ส่งผลประเมิน</button>
</div>
</div>
</div>