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

449 lines
25 KiB
PHP

<?php
// views/users/index.php
require_once dirname(__DIR__, 2) . '/models/User.php';
require_once dirname(__DIR__, 2) . '/models/Department.php';
// Check if user is admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
echo '<div class="glass-card p-8 text-center text-red-500">
<i class="fa-solid fa-lock text-4xl mb-4"></i>
<h2 class="text-xl font-bold">Access Denied</h2>
<p>คุณไม่มีสิทธิ์เข้าถึงหน้านี้</p>
</div>';
return;
}
$userModel = new User();
$deptModel = new Department();
$users = $userModel->getAll();
$departments = $deptModel->getAll();
?>
<div class="mb-6 flex justify-between items-center">
<div>
<h2 class="text-2xl font-bold text-slate-800 dark:text-white">ทะเบียนบุคลากร (User Management)</h2>
<p class="text-slate-500 dark:text-slate-400">จัดการข้อมูลผู้ใช้งานระบบ และเชื่อมต่อฐานข้อมูล HR</p>
</div>
<button onclick="openUserModal('add')" class="btn-primary-glass py-2 px-4 font-medium flex items-center">
<i class="fa-solid fa-user-plus me-2"></i> เพิ่มผู้ใช้งาน
</button>
</div>
<!-- Users Table -->
<div class="glass-card overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<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 bg-slate-50/50 dark:bg-slate-800/50">
<th class="py-4 px-6">ชื่อ - สกุล</th>
<th class="py-4 px-6">ชื่อผู้ใช้ (Username)</th>
<th class="py-4 px-6 text-center">สิทธิ์การใช้งาน</th>
<th class="py-4 px-6">แผนก (จุดบริการ)</th>
<th class="py-4 px-6 text-center">สถานะ</th>
<th class="py-4 px-6 text-right">จัดการ</th>
</tr>
</thead>
<tbody class="text-sm text-slate-700 dark:text-slate-300 divide-y divide-slate-200 dark:divide-slate-700">
<?php foreach ($users as $u): ?>
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
<td class="py-4 px-6 font-medium">
<div class="flex items-center gap-3">
<img src="api/avatar.php?username=<?= urlencode($u['username']) ?>&name=<?= urlencode($u['full_name']) ?>" class="rounded-full w-8 h-8 object-cover shadow-sm border border-slate-200 dark:border-slate-700">
<?= escape($u['full_name']) ?>
</div>
</td>
<td class="py-4 px-6"><?= escape($u['username']) ?></td>
<td class="py-4 px-6 text-center">
<?php
$roleClass = 'bg-slate-100 text-slate-800';
$roleName = 'ไม่ระบุ';
if ($u['role'] === 'admin') { $roleClass = 'bg-purple-100 text-purple-800'; $roleName = 'ผู้ดูแลระบบ'; }
if ($u['role'] === 'manager') { $roleClass = 'bg-blue-100 text-blue-800'; $roleName = 'หัวหน้างาน'; }
if ($u['role'] === 'nurse') { $roleClass = 'bg-pink-100 text-pink-800'; $roleName = 'พยาบาล'; }
if ($u['role'] === 'staff') { $roleClass = 'bg-emerald-100 text-emerald-800'; $roleName = 'เวรเปล'; }
?>
<span class="<?= $roleClass ?> px-2 py-1 rounded-full text-xs font-medium"><?= $roleName ?></span>
</td>
<td class="py-4 px-6 text-slate-500">
<?= escape($u['department_name'] ?? '-') ?>
</td>
<td class="py-4 px-6 text-center">
<?php if ($u['status'] === 'online'): ?>
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800">
<span class="w-1.5 h-1.5 rounded-full bg-green-500"></span> Online
</span>
<?php elseif ($u['status'] === 'busy'): ?>
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium bg-orange-100 text-orange-800">
<span class="w-1.5 h-1.5 rounded-full bg-orange-500"></span> Busy
</span>
<?php else: ?>
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-800 dark:bg-slate-700 dark:text-slate-300">
<span class="w-1.5 h-1.5 rounded-full bg-slate-400"></span> Offline
</span>
<?php endif; ?>
</td>
<td class="py-4 px-6 text-right space-x-2">
<button onclick="openUserModal('edit', <?= htmlspecialchars(json_encode([
'id' => $u['id'],
'username' => $u['username'],
'full_name' => $u['full_name'],
'role' => $u['role'],
'department_id' => $u['department_id']
])) ?>)" class="text-blue-500 hover:text-blue-700 p-1" title="แก้ไขข้อมูล">
<i class="fa-solid fa-edit"></i>
</button>
<button onclick="openPasswordModal(<?= $u['id'] ?>, '<?= escape($u['username']) ?>')" class="text-amber-500 hover:text-amber-700 p-1" title="เปลี่ยนรหัสผ่าน">
<i class="fa-solid fa-key"></i>
</button>
<?php if ($u['id'] != $_SESSION['user_id']): ?>
<button onclick="deleteUser(<?= $u['id'] ?>)" class="text-red-500 hover:text-red-700 p-1" title="ลบผู้ใช้งาน">
<i class="fa-solid fa-trash"></i>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- User Add/Edit Modal -->
<div id="userModal" class="fixed inset-0 z-50 hidden bg-slate-900/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="glass-card w-full max-w-2xl transform transition-all max-h-[90vh] overflow-y-auto">
<div class="p-6 border-b border-slate-200 dark:border-slate-700 flex justify-between items-center sticky top-0 bg-white/80 dark:bg-slate-900/80 backdrop-blur z-10">
<h3 class="text-xl font-bold text-slate-800 dark:text-white" id="userModalTitle">เพิ่มผู้ใช้งานใหม่</h3>
<button type="button" onclick="closeUserModal()" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
<i class="fa-solid fa-times fa-lg"></i>
</button>
</div>
<div class="p-6 space-y-6">
<!-- HR Search Box (Only for Add) -->
<div id="hrSearchBox" class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-xl p-4">
<label class="block text-sm font-medium text-blue-800 dark:text-blue-300 mb-2">
<i class="fa-solid fa-magnifying-glass me-1"></i> ค้นหาจากฐานข้อมูล HR (HOSxP)
</label>
<div class="flex gap-2">
<input type="text" id="hrCid" class="glass-input flex-1 px-4 py-2 bg-white dark:bg-slate-800" placeholder="ระบุเลขบัตรประชาชน หรือ ชื่อ-สกุล">
<button type="button" onclick="searchHR()" id="btnSearchHR" class="btn-primary-glass px-4 py-2 whitespace-nowrap">
ค้นหา
</button>
</div>
<div id="hrResult" class="mt-3 text-sm hidden">
<!-- Results populated by JS -->
</div>
</div>
<form id="userForm">
<input type="hidden" name="csrf_token" value="<?= generateCSRFToken() ?>">
<input type="hidden" name="action" id="userAction" value="add">
<input type="hidden" name="id" id="userId" value="">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Full Name -->
<div class="md:col-span-2">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อ - สกุล <span class="text-red-500">*</span></label>
<input type="text" name="full_name" id="userFullName" class="glass-input w-full px-4 py-2" required>
</div>
<!-- Username -->
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อผู้ใช้ (Username) <span class="text-red-500">*</span></label>
<input type="text" name="username" id="userUsername" class="glass-input w-full px-4 py-2" required>
<p class="text-xs text-slate-500 mt-1" id="usernameHint">แนะนำใช้เลขบัตรประชาชน หรือรหัสพนักงาน</p>
</div>
<!-- Password (Only for Add) -->
<div id="passwordField">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">รหัสผ่าน <span class="text-red-500">*</span></label>
<input type="password" name="password" id="userPassword" class="glass-input w-full px-4 py-2">
</div>
<!-- Role -->
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">สิทธิ์การใช้งาน <span class="text-red-500">*</span></label>
<select name="role" id="userRole" class="glass-input w-full px-4 py-2" required>
<option value="">-- เลือกสิทธิ์ --</option>
<option value="staff">เวรเปล (Staff)</option>
<option value="nurse">พยาบาล (Nurse)</option>
<option value="manager">หัวหน้างาน (Manager)</option>
<option value="admin">ผู้ดูแลระบบ (Admin)</option>
</select>
</div>
<!-- Department -->
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">แผนก / จุดบริการ (ระบบเปล)</label>
<select name="department_id" id="userDepartment" class="glass-input w-full px-4 py-2">
<option value="">-- ไม่ระบุ --</option>
<?php foreach ($departments as $d): ?>
<option value="<?= $d['id'] ?>"><?= escape($d['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mt-6 flex justify-end gap-3 pt-4 border-t border-slate-200 dark:border-slate-700">
<button type="button" onclick="closeUserModal()" class="btn-secondary-glass py-2 px-4">ยกเลิก</button>
<button type="submit" class="btn-primary-glass py-2 px-8">บันทึกข้อมูล</button>
</div>
</form>
</div>
</div>
</div>
<!-- Password Reset Modal -->
<div id="passwordModal" class="fixed inset-0 z-50 hidden bg-slate-900/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="glass-card w-full max-w-sm transform transition-all">
<div class="p-6 border-b border-slate-200 dark:border-slate-700 flex justify-between items-center">
<h3 class="text-lg font-bold text-slate-800 dark:text-white">เปลี่ยนรหัสผ่าน</h3>
<button type="button" onclick="closePasswordModal()" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
<i class="fa-solid fa-times"></i>
</button>
</div>
<form id="passwordForm" class="p-6 space-y-4">
<input type="hidden" name="csrf_token" value="<?= generateCSRFToken() ?>">
<input type="hidden" name="action" value="edit_password">
<input type="hidden" name="id" id="pwdUserId" value="">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อผู้ใช้งาน</label>
<input type="text" id="pwdUsername" class="glass-input w-full px-4 py-2 bg-slate-100 dark:bg-slate-800" readonly disabled>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">รหัสผ่านใหม่ <span class="text-red-500">*</span></label>
<input type="text" name="password" class="glass-input w-full px-4 py-2" required minlength="4">
</div>
<div class="flex justify-end gap-3 pt-4">
<button type="button" onclick="closePasswordModal()" class="btn-secondary-glass py-2 px-4">ยกเลิก</button>
<button type="submit" class="btn-primary-glass py-2 px-6">บันทึก</button>
</div>
</form>
</div>
</div>
<script>
function openUserModal(action, data = null) {
document.getElementById('userAction').value = action;
const form = document.getElementById('userForm');
if (action === 'add') {
document.getElementById('userModalTitle').textContent = 'เพิ่มผู้ใช้งานใหม่';
document.getElementById('hrSearchBox').classList.remove('hidden');
document.getElementById('passwordField').classList.remove('hidden');
document.getElementById('userUsername').readOnly = false;
document.getElementById('userUsername').classList.remove('bg-slate-100', 'dark:bg-slate-800');
document.getElementById('userPassword').required = true;
form.reset();
document.getElementById('userId').value = '';
document.getElementById('hrResult').classList.add('hidden');
} else if (action === 'edit' && data) {
document.getElementById('userModalTitle').textContent = 'แก้ไขข้อมูลผู้ใช้งาน';
document.getElementById('hrSearchBox').classList.add('hidden');
document.getElementById('passwordField').classList.add('hidden');
document.getElementById('userUsername').readOnly = true;
document.getElementById('userUsername').classList.add('bg-slate-100', 'dark:bg-slate-800');
document.getElementById('userPassword').required = false;
document.getElementById('userId').value = data.id;
document.getElementById('userUsername').value = data.username;
document.getElementById('userFullName').value = data.full_name;
document.getElementById('userRole').value = data.role;
document.getElementById('userDepartment').value = data.department_id || '';
}
document.getElementById('userModal').classList.remove('hidden');
}
function closeUserModal() {
document.getElementById('userModal').classList.add('hidden');
}
function selectHRResult(cid, fullName, position, department, workDuration) {
document.getElementById('userFullName').value = fullName;
document.getElementById('userUsername').value = cid;
document.getElementById('userPassword').value = '123456'; // Default pass is 123456
const resultBox = document.getElementById('hrResult');
resultBox.innerHTML = `
<div class="bg-green-50 dark:bg-green-900/30 text-green-800 dark:text-green-300 p-3 rounded border border-green-200 dark:border-green-800">
<p><strong><i class="fa-solid fa-check-circle"></i> เลือก: ${fullName}</strong></p>
<ul class="mt-2 space-y-1 list-disc list-inside">
<li><strong>ชื่อ:</strong> ${fullName}</li>
<li><strong>ตำแหน่ง:</strong> ${position}</li>
<li><strong>หน่วยงาน (HR):</strong> ${department}</li>
<li><strong>อายุงาน:</strong> ${workDuration}</li>
</ul>
<p class="mt-2 text-xs opacity-80">* ระบบได้นำเลข CID ไปตั้งเป็น Username (รหัสผ่านเริ่มต้นคือ 123456)</p>
</div>
`;
}
function openPasswordModal(id, username) {
document.getElementById('passwordForm').reset();
document.getElementById('pwdUserId').value = id;
document.getElementById('pwdUsername').value = username;
document.getElementById('passwordModal').classList.remove('hidden');
}
function closePasswordModal() {
document.getElementById('passwordModal').classList.add('hidden');
}
function searchHR() {
const query = document.getElementById('hrCid').value.trim();
if (!query) {
Swal.fire('แจ้งเตือน', 'กรุณาระบุเลขบัตรประชาชน หรือ ชื่อ-สกุล', 'warning');
return;
}
const btn = document.getElementById('btnSearchHR');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> กำลังค้นหา...';
btn.disabled = true;
$.ajax({
url: 'api/hr.php',
type: 'POST',
data: { query: query },
dataType: 'json',
success: function(response) {
btn.innerHTML = originalText;
btn.disabled = false;
const resultBox = document.getElementById('hrResult');
if (response.status === 'success') {
const data = response.data;
selectHRResult(data.cid, data.full_name, data.position, data.department, data.work_duration);
resultBox.classList.remove('hidden');
} else if (response.status === 'multiple') {
let html = `
<div class="bg-amber-50 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300 p-3 rounded border border-amber-200 dark:border-amber-800 mb-2">
<strong><i class="fa-solid fa-users"></i> ${response.message}</strong>
</div>
<div class="space-y-2 max-h-60 overflow-y-auto pr-2 custom-scrollbar">
`;
response.data.forEach(item => {
html += `
<button type="button" onclick="selectHRResult('${item.cid}', '${item.full_name}', '${item.position}', '${item.department}', '${item.work_duration}')"
class="w-full text-left bg-white dark:bg-slate-800 p-3 rounded-lg border border-slate-200 dark:border-slate-700 hover:border-emerald-500 hover:shadow-md transition-all">
<div class="font-medium text-slate-800 dark:text-white">${item.full_name}</div>
<div class="text-xs text-slate-500 mt-1">
<span class="mr-3"><i class="fa-solid fa-briefcase mr-1"></i> ${item.position}</span>
<span><i class="fa-solid fa-hospital-user mr-1"></i> ${item.department}</span>
</div>
</button>
`;
});
html += `</div>`;
resultBox.innerHTML = html;
resultBox.classList.remove('hidden');
} else {
resultBox.innerHTML = `
<div class="bg-red-50 dark:bg-red-900/30 text-red-800 dark:text-red-300 p-3 rounded border border-red-200 dark:border-red-800">
<i class="fa-solid fa-triangle-exclamation"></i> ${response.message}
</div>
`;
resultBox.classList.remove('hidden');
}
},
error: function() {
btn.innerHTML = originalText;
btn.disabled = false;
Swal.fire('ผิดพลาด', 'ไม่สามารถเชื่อมต่อระบบ HR ได้', 'error');
}
});
}
function deleteUser(id) {
Swal.fire({
title: 'ยืนยันการลบ?',
text: "คุณต้องการลบผู้ใช้งานนี้ใช่หรือไม่",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ef4444',
cancelButtonColor: '#94a3b8',
confirmButtonText: 'ลบ',
cancelButtonText: 'ยกเลิก',
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b'
}).then((result) => {
if (result.isConfirmed) {
$.post('api/users.php', {
action: 'delete',
id: id,
csrf_token: '<?= generateCSRFToken() ?>'
}, function(response) {
if (response.status === 'success') {
Swal.fire({icon: 'success', title: 'สำเร็จ', text: response.message, timer: 1500, showConfirmButton: false})
.then(() => window.location.reload());
} else {
Swal.fire('ผิดพลาด', response.message, 'error');
}
}, 'json');
}
});
}
document.addEventListener('DOMContentLoaded', function() {
$('#userForm').on('submit', function(e) {
e.preventDefault();
const btn = $(this).find('button[type="submit"]');
btn.addClass('btn-loading');
$.ajax({
url: 'api/users.php',
type: 'POST',
data: $(this).serialize(),
dataType: 'json',
success: function(response) {
btn.removeClass('btn-loading');
if (response.status === 'success') {
closeUserModal();
Swal.fire({icon: 'success', title: 'สำเร็จ', text: response.message, timer: 1500, showConfirmButton: false})
.then(() => window.location.reload());
} else {
Swal.fire('ผิดพลาด', response.message, 'error');
}
},
error: function() {
btn.removeClass('btn-loading');
Swal.fire('ผิดพลาด', 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้', 'error');
}
});
});
$('#passwordForm').on('submit', function(e) {
e.preventDefault();
const btn = $(this).find('button[type="submit"]');
btn.addClass('btn-loading');
$.ajax({
url: 'api/users.php',
type: 'POST',
data: $(this).serialize(),
dataType: 'json',
success: function(response) {
btn.removeClass('btn-loading');
if (response.status === 'success') {
closePasswordModal();
Swal.fire({icon: 'success', title: 'สำเร็จ', text: response.message, timer: 1500, showConfirmButton: false});
} else {
Swal.fire('ผิดพลาด', response.message, 'error');
}
},
error: function() {
btn.removeClass('btn-loading');
Swal.fire('ผิดพลาด', 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้', 'error');
}
});
});
});
</script>