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

305 lines
24 KiB
PHP

<?php require_once APP_ROOT . '/app/Views/layouts/header.php'; ?>
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6 gap-4">
<div>
<h2 class="text-2xl font-bold text-slate-800 dark:text-white">จัดการผู้ใช้งานระบบ</h2>
<p class="text-slate-500 dark:text-slate-400 text-sm mt-1">กำหนดสิทธิ์และเพิ่มผู้ใช้งานที่สามารถเข้าระบบได้</p>
</div>
<div class="flex gap-2">
<a href="<?= BASE_URL ?>/users/logs" class="inline-flex items-center justify-center px-4 py-2.5 bg-white border border-slate-300 dark:border-slate-600 dark:bg-slate-800 hover:bg-slate-50 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 text-sm font-medium rounded-xl transition-colors shadow-sm w-full md:w-auto">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 mr-2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /></svg>
ประวัติการใช้งาน (Logs)
</a>
<button onclick="document.getElementById('createUserModal').classList.remove('hidden')" class="inline-flex items-center justify-center px-4 py-2.5 bg-sky-500 hover:bg-sky-600 text-white text-sm font-medium rounded-xl transition-colors shadow-sm w-full md:w-auto">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 mr-2"><path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z" /></svg>
เพิ่มผู้ใช้งาน
</button>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-100 dark:border-slate-700 overflow-hidden">
<div class="flex justify-end p-4 pb-0">
<label class="inline-flex items-center cursor-pointer">
<input type="checkbox" id="togglePrefix" class="w-4 h-4 text-indigo-600 bg-slate-100 border-slate-300 rounded focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:ring-offset-slate-800 focus:ring-2 dark:bg-slate-700 dark:border-slate-600">
<span class="ml-2 text-sm font-medium text-slate-700 dark:text-slate-300">แสดงคำนำหน้าชื่อ</span>
</label>
</div>
<div class="overflow-x-auto p-4 pt-2">
<table class="w-full text-left text-sm text-slate-600 dark:text-slate-300">
<thead class="bg-slate-50 dark:bg-slate-900/50 text-slate-500 dark:text-slate-400 text-xs uppercase font-semibold">
<tr>
<th class="px-6 py-4">Username</th>
<th class="px-6 py-4">ชื่อ - นามสกุล</th>
<th class="px-6 py-4">สิทธิ์ (Role)</th>
<th class="px-6 py-4 text-center">สถานะ</th>
<th class="px-6 py-4 text-right">จัดการ</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-700">
<?php if (empty($users)): ?>
<tr>
<td colspan="5" class="px-6 py-8 text-center text-slate-500 dark:text-slate-400">
ไม่พบข้อมูล
</td>
</tr>
<?php else: ?>
<?php foreach ($users as $user): ?>
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors">
<td class="px-6 py-4 font-mono font-bold text-sky-600 dark:text-sky-400">
<?= htmlspecialchars($user['username']) ?>
</td>
<td class="px-6 py-4" data-sort="<?= htmlspecialchars(preg_replace('/^(นาย|นางสาว|นาง|ว่าที่ร้อยตรี|น\.ส\.|นพ\.|พญ\.|ดร\.)\s*/u', '', trim($user['first_name']))) ?>">
<?php
$rawName = trim($user['first_name']);
$prefix = '';
$bodyName = $rawName;
if (preg_match('/^(นาย|นางสาว|นาง|ว่าที่ร้อยตรี|น\.ส\.|นพ\.|พญ\.|ดร\.)\s*(.*)$/u', $rawName, $matches)) {
$prefix = $matches[1];
$bodyName = $matches[2];
}
?>
<div class="font-medium text-slate-800 dark:text-slate-200">
<span class="name-prefix hidden"><?= htmlspecialchars($prefix) ?></span><?= htmlspecialchars($bodyName . ' ' . $user['last_name']) ?>
</div>
</td>
<td class="px-6 py-4">
<?php if ($user['role_id'] == 1): ?>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400">
<?= htmlspecialchars($user['role_name']) ?>
</span>
<?php else: ?>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-700 dark:bg-slate-700 dark:text-slate-300">
<?= htmlspecialchars($user['role_name']) ?>
</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 text-center">
<?php if ($user['status'] == 'active'): ?>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400">Active</span>
<?php else: ?>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400">Inactive</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 text-right">
<div class="flex items-center justify-end space-x-2">
<?php if ($user['status'] == 'active'): ?>
<form action="<?= BASE_URL ?>/users/toggle_status" method="POST" class="inline">
<input type="hidden" name="user_id" value="<?= $user['id'] ?>">
<input type="hidden" name="status" value="inactive">
<button type="submit" onclick="return confirm('ยืนยันการปิดสิทธิ์ผู้ใช้งานนี้?')" class="inline-flex items-center px-3 py-1.5 border border-red-200 dark:border-red-800/30 bg-red-50 text-red-600 dark:bg-red-900/20 dark:text-red-400 rounded-lg text-xs font-medium hover:bg-red-100 dark:hover:bg-red-900/40 transition-colors" title="ปิดสิทธิ์">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-3.5 h-3.5 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" /></svg>
ปิดสิทธิ์
</button>
</form>
<?php else: ?>
<form action="<?= BASE_URL ?>/users/toggle_status" method="POST" class="inline">
<input type="hidden" name="user_id" value="<?= $user['id'] ?>">
<input type="hidden" name="status" value="active">
<button type="submit" class="inline-flex items-center px-3 py-1.5 border border-emerald-200 dark:border-emerald-800/30 bg-emerald-50 text-emerald-600 dark:bg-emerald-900/20 dark:text-emerald-400 rounded-lg text-xs font-medium hover:bg-emerald-100 dark:hover:bg-emerald-900/40 transition-colors" title="เปิดสิทธิ์">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-3.5 h-3.5 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0Z" /></svg>
เปิดสิทธิ์
</button>
</form>
<?php endif; ?>
<button onclick="openEditModal(<?= htmlspecialchars(json_encode($user)) ?>)" class="inline-flex items-center px-3 py-1.5 border border-slate-300 dark:border-slate-600 rounded-lg text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">
แก้ไข
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Create Modal -->
<div id="createUserModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm transition-opacity" onclick="document.getElementById('createUserModal').classList.add('hidden')"></div>
<div class="flex min-h-screen items-center justify-center p-4">
<div class="relative transform overflow-hidden rounded-2xl bg-white dark:bg-slate-800 text-left shadow-xl transition-all sm:w-full sm:max-w-lg border border-slate-100 dark:border-slate-700">
<form action="<?= BASE_URL ?>/users" method="POST">
<div class="p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-6">เพิ่มผู้ใช้งานใหม่</h3>
<div class="space-y-4">
<div>
<label for="hr_search" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">ค้นหาจากระบบบุคลากร (HR)</label>
<select id="hr_search" class="searchable-select block w-full"></select>
<p class="text-xs text-slate-500 mt-1">พิมพ์ชื่อ, นามสกุล เพื่อดึงข้อมูลอัตโนมัติ</p>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="first_name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">ชื่อ (First Name)</label>
<input type="text" id="first_name" name="first_name" required class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
</div>
<div>
<label for="last_name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">นามสกุล (Last Name)</label>
<input type="text" id="last_name" name="last_name" required class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
</div>
</div>
<div>
<label for="username" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">ชื่อผู้ใช้ (Username)</label>
<input type="text" id="username" name="username" required autocomplete="off" class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
<p class="text-xs text-slate-500 mt-1">แนะนำให้ใช้รหัสบัตรประชาชน 13 หลัก</p>
</div>
<div>
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">รหัสผ่าน (Password)</label>
<input type="password" id="password" name="password" required class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
</div>
<div>
<label for="role_id" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">สิทธิ์การใช้งาน (Role)</label>
<select id="role_id" name="role_id" required class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
<?php foreach($roles as $role): ?>
<option value="<?= $role['id'] ?>"><?= htmlspecialchars($role['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="flex items-center justify-between mt-4">
<label class="flex items-center cursor-pointer">
<div class="relative">
<input type="checkbox" name="status" class="sr-only" checked>
<div class="block bg-slate-200 dark:bg-slate-700 w-10 h-6 rounded-full transition-colors"></div>
<div class="dot absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition-transform transform translate-x-full"></div>
</div>
<div class="ml-3 text-sm font-medium text-slate-700 dark:text-slate-300">เปิดใช้งาน Account นี้</div>
</label>
</div>
</div>
</div>
<div class="bg-slate-50 dark:bg-slate-700/50 px-6 py-4 flex justify-end space-x-3">
<button type="button" onclick="document.getElementById('createUserModal').classList.add('hidden')" class="px-4 py-2 text-sm font-semibold text-slate-700 dark:text-slate-300 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">ยกเลิก</button>
<button type="submit" class="px-4 py-2 text-sm font-semibold text-white bg-sky-500 rounded-xl hover:bg-sky-600 transition-colors">บันทึก</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Modal -->
<div id="editUserModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm transition-opacity" onclick="document.getElementById('editUserModal').classList.add('hidden')"></div>
<div class="flex min-h-screen items-center justify-center p-4">
<div class="relative transform overflow-hidden rounded-2xl bg-white dark:bg-slate-800 text-left shadow-xl transition-all sm:w-full sm:max-w-lg border border-slate-100 dark:border-slate-700">
<form action="<?= BASE_URL ?>/users/update" method="POST">
<input type="hidden" id="edit_id" name="id">
<div class="p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-6">แก้ไขผู้ใช้งาน</h3>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label for="edit_first_name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">ชื่อ (First Name)</label>
<input type="text" id="edit_first_name" name="first_name" required class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
</div>
<div>
<label for="edit_last_name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">นามสกุล (Last Name)</label>
<input type="text" id="edit_last_name" name="last_name" required class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">ชื่อผู้ใช้ (Username)</label>
<input type="text" id="edit_username" disabled class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-500 dark:text-slate-400 shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 bg-slate-100 dark:bg-slate-800/50 sm:text-sm cursor-not-allowed">
</div>
<div>
<label for="edit_password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">รหัสผ่านใหม่ (ปล่อยว่างถ้าไม่ต้องการเปลี่ยน)</label>
<input type="password" id="edit_password" name="password" class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
</div>
<div>
<label for="edit_role_id" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">สิทธิ์การใช้งาน (Role)</label>
<select id="edit_role_id" name="role_id" required class="block w-full rounded-xl border-0 py-2.5 px-3 text-slate-900 dark:text-white shadow-sm ring-1 ring-inset ring-slate-300 dark:ring-slate-600 focus:ring-2 focus:ring-sky-500 bg-slate-50 dark:bg-slate-900 sm:text-sm">
<?php foreach($roles as $role): ?>
<option value="<?= $role['id'] ?>"><?= htmlspecialchars($role['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="bg-slate-50 dark:bg-slate-700/50 px-6 py-4 flex justify-end space-x-3">
<button type="button" onclick="document.getElementById('editUserModal').classList.add('hidden')" class="px-4 py-2 text-sm font-semibold text-slate-700 dark:text-slate-300 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">ยกเลิก</button>
<button type="submit" class="px-4 py-2 text-sm font-semibold text-white bg-sky-500 rounded-xl hover:bg-sky-600 transition-colors">บันทึกการแก้ไข</button>
</div>
</form>
</div>
</div>
</div>
<style>
/* Custom Toggle Switch Logic */
input:checked ~ .block { background-color: #0ea5e9; }
input:checked ~ .dot { transform: translateX(100%); }
input:not(:checked) ~ .dot { transform: translateX(0); }
</style>
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
function openEditModal(user) {
document.getElementById('edit_id').value = user.id;
document.getElementById('edit_first_name').value = user.first_name;
document.getElementById('edit_last_name').value = user.last_name;
document.getElementById('edit_username').value = user.username;
document.getElementById('edit_role_id').value = user.role_id;
document.getElementById('editUserModal').classList.remove('hidden');
}
$(document).ready(function() {
$('#hr_search').select2({
placeholder: 'ค้นหาเจ้าหน้าที่จากระบบ HR',
allowClear: true,
ajax: {
url: '<?= BASE_URL ?>/settings/system/search-officer',
dataType: 'json',
delay: 250,
data: function (params) {
return { q: params.term };
},
processResults: function (data) {
return { results: data.data || [] };
},
cache: true
}
});
$('#hr_search').on('select2:select', function (e) {
var data = e.params.data;
// Name format usually comes like "คำนำหน้า ชื่อ สกุล (ตำแหน่ง)"
// We'll extract basic names from the full string if we can't separate it easily,
// but the name returned in search-officer is "คำนำหน้า ชื่อ สกุล"
var parts = data.name.trim().split(' ');
if (parts.length >= 2) {
var fname = parts[0];
var lname = parts.slice(1).join(' ');
$('#first_name').val(fname.replace(/นาย|นางสาว|นาง|ว่าที่ร.ต.|พ.ต.ต.|ร.ต.อ./g, ''));
$('#last_name').val(lname);
} else {
$('#first_name').val(data.name);
}
$('#username').val(data.id); // ID is usually HR_CID
});
// Checkbox toggle prefix
$('#togglePrefix').on('change', function() {
if ($(this).is(':checked')) {
$('.name-prefix').removeClass('hidden');
} else {
$('.name-prefix').addClass('hidden');
}
});
});
</script>
<?php require_once APP_ROOT . '/app/Views/layouts/footer.php'; ?>