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,53 @@
<?php
session_start();
require_once '../includes/db.php';
if (!isset($_SESSION['admin_logged_in'])) {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
// Find CIDs that have duplicates
$stmt = $pdo_bot->query("
SELECT cid, COUNT(*) as c
FROM line_staff_register
GROUP BY cid
HAVING c > 1
");
$duplicates = $stmt->fetchAll();
$deleted_total = 0;
foreach ($duplicates as $dup) {
$cid = $dup['cid'];
// Get all records for this CID ordered by date DESC, then id DESC
$stmt = $pdo_bot->prepare("
SELECT id
FROM line_staff_register
WHERE cid = ?
ORDER BY date DESC, id DESC
");
$stmt->execute([$cid]);
$records = $stmt->fetchAll();
if (count($records) > 1) {
// Keep the first one (most recent date)
$keep_id = $records[0]['id'];
// Delete the rest
for ($i = 1; $i < count($records); $i++) {
$del_stmt = $pdo_bot->prepare("DELETE FROM line_staff_register WHERE id = ?");
$del_stmt->execute([$records[$i]['id']]);
$deleted_total++;
}
}
}
echo json_encode(['success' => true, 'deleted_count' => $deleted_total, 'message' => "ลบข้อมูลที่ซ้ำซ้อนสำเร็จ จำนวน $deleted_total รายการ"]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Error: ' . $e->getMessage()]);
}
}
@@ -0,0 +1,45 @@
<?php
session_start();
require_once '../includes/db.php';
if (!isset($_SESSION['admin_logged_in'])) {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$cid = $_POST['cid'] ?? '';
if (empty($cid)) {
echo json_encode(['success' => false, 'message' => 'ไม่พบข้อมูล CID']);
exit;
}
if (!$conn_hosoffice) {
echo json_encode(['success' => false, 'message' => 'ไม่สามารถเชื่อมต่อฐานข้อมูล HR (hosoffice) ได้']);
exit;
}
$cid_safe = mysqli_real_escape_string($conn_hosoffice, $cid);
$query = "SELECT HR_STATUS_ID FROM hr_person WHERE HR_CID = '$cid_safe' LIMIT 1";
$result = mysqli_query($conn_hosoffice, $query);
if ($result && mysqli_num_rows($result) > 0) {
$row = mysqli_fetch_assoc($result);
$hr_status = $row['HR_STATUS_ID'];
$new_status = ($hr_status === '01') ? 'Y' : 'N';
// Update line_staff_register
$stmt = $pdo_bot->prepare("UPDATE line_staff_register SET ksh_user = ? WHERE cid = ?");
$stmt->execute([$new_status, $cid]);
echo json_encode(['success' => true, 'new_status' => $new_status, 'message' => 'อัปเดตสถานะเรียบร้อย']);
} else {
// Not found in hr_person
$stmt = $pdo_bot->prepare("UPDATE line_staff_register SET ksh_user = 'N' WHERE cid = ?");
$stmt->execute([$cid]);
echo json_encode(['success' => true, 'new_status' => 'N', 'message' => 'ไม่พบ CID นี้ในระบบ HR (อัปเดตเป็นพ้นสภาพ)']);
}
}
@@ -0,0 +1,133 @@
<?php
session_start();
require_once '../includes/db.php';
if (!isset($_SESSION['admin_logged_in'])) {
header("Location: login.php");
exit;
}
// Ensure 2FA is setup
$stmt_admin = $pdo->prepare("SELECT username, role, google2fa_secret FROM admin_users WHERE id = ?");
$stmt_admin->execute([$_SESSION['admin_id']]);
$current_admin = $stmt_admin->fetch();
if (empty($current_admin['google2fa_secret'])) {
header("Location: setup_2fa.php");
exit;
}
$is_superadmin = ($current_admin['role'] === 'superadmin');
// Handle settings update
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['update_settings'])) {
$require = isset($_POST['require_assessment']) ? '1' : '0';
$stmt = $pdo->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'require_assessment'");
$stmt->execute([$require]);
$msg = "บันทึกการตั้งค่าเรียบร้อยแล้ว";
}
$require_assessment = getSetting($pdo, 'require_assessment');
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - KSH-STAFF</title>
<link rel="icon" type="image/png" href="../assets/img/logo.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Sarabun', 'sans-serif'],
},
animation: {
shine: 'shine 3s ease-in-out infinite'
},
keyframes: {
shine: {
'0%': { left: '-150%' },
'20%': { left: '150%' },
'100%': { left: '150%' }
}
}
}
}
}
</script>
</head>
<body class="bg-gray-100 font-sans antialiased text-gray-800 flex min-h-screen">
<!-- Sidebar -->
<aside class="w-64 bg-gray-800 text-gray-300 flex flex-col fixed top-0 left-0 h-screen shadow-lg z-20">
<div class="px-6 py-5 bg-gray-900 border-b border-gray-700 text-center">
<div class="relative inline-block overflow-hidden rounded-full w-20 h-20 mx-auto mb-3 shadow-md bg-white p-1">
<img src="../assets/img/logo.png" alt="Logo" class="w-full h-full object-cover rounded-full relative z-10">
<div class="absolute inset-0 z-20 w-1/2 h-full bg-gradient-to-r from-transparent via-white/60 to-transparent -skew-x-12 animate-shine mix-blend-overlay"></div>
</div>
<h3 class="text-white text-lg font-bold tracking-wide mt-1">KSH-STAFF Admin</h3>
</div>
<nav class="flex-1 pt-6 pb-4 overflow-y-auto flex flex-col gap-1">
<a href="index.php" class="block px-6 py-3 bg-blue-600 text-white font-medium transition duration-200 shadow-sm">
<i class="fas fa-home w-6 text-center mr-2"></i> แดชบอร์ด
</a>
<a href="staff_list.php" class="block px-6 py-3 hover:bg-gray-700 hover:text-white transition duration-200">
<i class="fas fa-users w-6 text-center mr-2"></i> รายชื่อเจ้าหน้าที่
</a>
<?php if ($is_superadmin): ?>
<a href="manage_admins.php" class="block px-6 py-3 hover:bg-gray-700 hover:text-white transition duration-200">
<i class="fas fa-crown w-6 text-center mr-2"></i> จัดการแอดมิน
</a>
<?php endif; ?>
<div class="mt-auto px-6 pt-4">
<a href="logout.php" class="flex items-center text-red-400 hover:text-red-300 hover:bg-red-400/10 py-2 px-3 rounded transition duration-200">
<i class="fas fa-sign-out-alt w-6 text-center mr-1"></i> ออกจากระบบ
</a>
</div>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 ml-64 flex flex-col min-h-screen">
<header class="bg-white px-8 py-5 border-b border-gray-200 flex justify-between items-center shadow-sm z-10 sticky top-0">
<h4 class="text-xl font-bold text-gray-700">การตั้งค่าระบบ</h4>
<div class="text-sm text-gray-500 font-medium px-4 py-1.5 bg-gray-100 rounded-full border border-gray-200 flex items-center gap-2">
ผู้ใช้งาน: <span class="text-blue-600 font-bold"><?= htmlspecialchars($current_admin['username'] ?? $_SESSION['admin_username'] ?? 'Admin') ?></span>
<span class="bg-gray-200 text-gray-600 px-2 py-0.5 rounded text-xs"><?= htmlspecialchars($current_admin['role'] ?? '') ?></span>
</div>
</header>
<div class="p-8 flex-1">
<?php if (!empty($msg)): ?>
<script>
Swal.fire({ icon: 'success', title: 'สำเร็จ', text: '<?= $msg ?>', timer: 2000, confirmButtonColor: '#2563eb' });
</script>
<?php endif; ?>
<div class="bg-white rounded-xl p-8 shadow-sm border border-gray-200 max-w-3xl">
<h3 class="text-lg font-bold text-gray-800 border-b border-gray-100 pb-4 mb-6"><i class="fas fa-cog text-gray-400 mr-2"></i> ตั้งค่าระบบประเมิน</h3>
<form method="post">
<div class="flex items-center gap-4 mb-8 bg-gray-50 p-4 rounded-lg border border-gray-100">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" name="require_assessment" value="1" class="sr-only peer" <?= $require_assessment == '1' ? 'checked' : '' ?>>
<div class="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all peer-checked:bg-blue-600"></div>
</label>
<span class="text-gray-700 font-medium">เปิดใช้งานการบังคับทำแบบประเมิน <span class="text-gray-500 text-sm font-normal block mt-1">(เมนูต่างๆ จะถูกล็อกหากเจ้าหน้าที่ยังไม่ทำประเมิน)</span></span>
</div>
<button type="submit" name="update_settings" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-6 rounded-lg transition duration-200 shadow-sm flex items-center gap-2">
<i class="fas fa-save"></i> บันทึกการตั้งค่า
</button>
</form>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,181 @@
<?php
session_start();
require_once '../includes/db.php';
require_once '../includes/GoogleAuthenticator.php';
$ga = new PHPGangsta_GoogleAuthenticator();
$step = 1;
$error = '';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (isset($_POST['login_step_1'])) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ?");
$stmt->execute([$username]);
$admin = $stmt->fetch();
if ($admin && password_verify($password, $admin['password_hash'])) {
// Step 1 Success
$_SESSION['temp_admin_id'] = $admin['id'];
$_SESSION['temp_admin_username'] = $admin['username'];
$_SESSION['temp_admin_role'] = $admin['role'];
$_SESSION['temp_admin_secret'] = $admin['google2fa_secret'];
if (empty($admin['google2fa_secret'])) {
// Not setup 2FA yet, go to setup
$_SESSION['admin_logged_in'] = true;
$_SESSION['admin_id'] = $admin['id'];
$_SESSION['admin_username'] = $admin['username'];
$_SESSION['admin_role'] = $admin['role'];
unset($_SESSION['temp_admin_id'], $_SESSION['temp_admin_username'], $_SESSION['temp_admin_role'], $_SESSION['temp_admin_secret']);
header("Location: setup_2fa.php");
exit;
} else {
$step = 2; // Move to 2FA step
}
} else {
$error = "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง";
}
} elseif (isset($_POST['login_step_2'])) {
$code = $_POST['code2fa'] ?? '';
$secret = $_SESSION['temp_admin_secret'];
if ($ga->verifyCode($secret, $code, 2)) {
// Success
$_SESSION['admin_logged_in'] = true;
$_SESSION['admin_id'] = $_SESSION['temp_admin_id'];
$_SESSION['admin_username'] = $_SESSION['temp_admin_username'];
$_SESSION['admin_role'] = $_SESSION['temp_admin_role'];
unset($_SESSION['temp_admin_id'], $_SESSION['temp_admin_username'], $_SESSION['temp_admin_role'], $_SESSION['temp_admin_secret']);
header("Location: index.php");
exit;
} else {
$error = "รหัส Google Authenticator ไม่ถูกต้อง";
$step = 2; // Stay at step 2
}
}
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login - KSH-STAFF</title>
<link rel="icon" type="image/png" href="../assets/img/logo.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Sarabun', 'sans-serif'],
},
animation: {
shine: 'shine 3s ease-in-out infinite'
},
keyframes: {
shine: {
'0%': { left: '-150%' },
'20%': { left: '150%' },
'100%': { left: '150%' }
}
}
}
}
}
</script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center font-sans antialiased">
<div class="bg-white rounded-xl shadow-lg p-8 w-full max-w-md border border-gray-100">
<div class="text-center mb-6">
<div class="relative inline-block overflow-hidden rounded-full w-24 h-24 mx-auto mb-4 shadow-md bg-white p-1">
<img src="../assets/img/logo.png" alt="Logo" class="w-full h-full object-cover rounded-full relative z-10">
<div class="absolute inset-0 z-20 w-1/2 h-full bg-gradient-to-r from-transparent via-white/60 to-transparent -skew-x-12 animate-shine mix-blend-overlay"></div>
</div>
<h2 class="text-blue-600 text-2xl font-bold">Admin Login</h2>
</div>
<?php if (!empty($error)): ?>
<div class="text-red-500 text-center text-sm mb-4 font-medium bg-red-50 py-2 rounded-lg"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if ($step === 1): ?>
<form method="post" class="space-y-4">
<div>
<label class="block text-gray-700 text-sm font-medium mb-1">Username (หรือ CID)</label>
<input type="text" name="username" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition" required autofocus>
</div>
<div>
<label class="block text-gray-700 text-sm font-medium mb-1">Password</label>
<input type="password" name="password" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition" required>
</div>
<button type="submit" name="login_step_1" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-4 rounded-lg transition duration-200 shadow-md hover:shadow-lg mt-2">เข้าสู่ระบบ</button>
</form>
<?php else: ?>
<form method="post" id="otp-form" class="space-y-6">
<div class="text-center text-gray-600 text-sm leading-relaxed">
กรุณาเปิดแอปพลิเคชัน <strong class="text-gray-800">Google Authenticator</strong><br>และนำรหัส 6 หลักมากรอก
</div>
<div class="flex justify-center gap-2">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp2')" id="otp1" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition" autofocus>
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp3')" onkeydown="moveToPrev(event, this, 'otp1')" id="otp2" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp4')" onkeydown="moveToPrev(event, this, 'otp2')" id="otp3" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp5')" onkeydown="moveToPrev(event, this, 'otp3')" id="otp4" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp6')" onkeydown="moveToPrev(event, this, 'otp4')" id="otp5" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="submitOtp()" onkeydown="moveToPrev(event, this, 'otp5')" id="otp6" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
</div>
<input type="hidden" name="code2fa" id="code2fa" required>
<button type="submit" name="login_step_2" id="submit-btn" class="hidden">ยืนยันตัวตน</button>
<div class="text-center mt-6">
<a href="login.php" class="text-sm text-gray-500 hover:text-gray-800 hover:underline transition">ยกเลิก / กลับไปหน้าแรก</a>
</div>
</form>
<script>
function moveToNext(current, nextFieldId) {
if (current.value.length === 1) {
if (nextFieldId) document.getElementById(nextFieldId).focus();
}
}
function moveToPrev(e, current, prevFieldId) {
if (e.key === 'Backspace' && current.value === '') {
if (prevFieldId) document.getElementById(prevFieldId).focus();
}
}
function submitOtp() {
let code = '';
for (let i = 1; i <= 6; i++) {
code += document.getElementById('otp' + i).value;
}
document.getElementById('code2fa').value = code;
if (code.length === 6) {
document.getElementById('submit-btn').click();
}
}
// Handle pasting 6 digits at once
document.getElementById('otp1').addEventListener('paste', function(e) {
let paste = (e.clipboardData || window.clipboardData).getData('text');
if (paste.length === 6 && /^\d+$/.test(paste)) {
for (let i = 0; i < 6; i++) {
document.getElementById('otp' + (i+1)).value = paste[i];
}
submitOtp();
e.preventDefault();
}
});
</script>
<?php endif; ?>
</div>
</body>
</html>
@@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header("Location: login.php");
exit;
@@ -0,0 +1,212 @@
<?php
session_start();
require_once '../includes/db.php';
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_role'] !== 'superadmin') {
header("Location: index.php");
exit;
}
$msg = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['add_admin'])) {
$cid = trim($_POST['cid']);
$password = $_POST['password'];
// Check if user already exists
$stmt_check = $pdo->prepare("SELECT id FROM admin_users WHERE username = ?");
$stmt_check->execute([$cid]);
if ($stmt_check->fetch()) {
$error = "เจ้าของหมายเลข CID นี้เป็น Admin อยู่แล้ว";
} else {
$hash = password_hash($password, PASSWORD_DEFAULT);
$insert = $pdo->prepare("INSERT INTO admin_users (username, cid, password_hash, role) VALUES (?, ?, ?, 'admin')");
if ($insert->execute([$cid, $cid, $hash])) {
$msg = "เพิ่มสิทธิ์ผู้ดูแลระบบสำเร็จ";
} else {
$error = "เกิดข้อผิดพลาดในการบันทึกข้อมูล";
}
}
}
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['delete_admin'])) {
$admin_id = $_POST['admin_id'];
$del = $pdo->prepare("DELETE FROM admin_users WHERE id = ? AND role != 'superadmin'");
if ($del->execute([$admin_id])) {
$msg = "ลบสิทธิ์ผู้ดูแลระบบสำเร็จ";
}
}
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['reset_2fa'])) {
$admin_id = $_POST['admin_id'];
$reset = $pdo->prepare("UPDATE admin_users SET google2fa_secret = NULL WHERE id = ?");
if ($reset->execute([$admin_id])) {
$msg = "รีเซ็ต 2FA สำเร็จ (เจ้าหน้าที่จะต้องตั้งค่า 2FA ใหม่เมื่อเข้าสู่ระบบครั้งถัดไป)";
}
}
$stmt = $pdo->query("SELECT * FROM admin_users ORDER BY role DESC, created_at ASC");
$admins = $stmt->fetchAll();
// Add $current_admin logic
$stmt_admin = $pdo->prepare("SELECT username, role FROM admin_users WHERE id = ?");
$stmt_admin->execute([$_SESSION['admin_id'] ?? 0]);
$current_admin = $stmt_admin->fetch();
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manage Admins - KSH-STAFF</title>
<link rel="icon" type="image/png" href="../assets/img/logo.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Sarabun', 'sans-serif'],
},
animation: {
shine: 'shine 3s ease-in-out infinite'
},
keyframes: {
shine: {
'0%': { left: '-150%' },
'20%': { left: '150%' },
'100%': { left: '150%' }
}
}
}
}
}
</script>
</head>
<body class="bg-gray-100 font-sans antialiased text-gray-800 flex min-h-screen">
<!-- Sidebar -->
<aside class="w-64 bg-gray-800 text-gray-300 flex flex-col fixed top-0 left-0 h-screen shadow-lg z-20">
<div class="px-6 py-5 bg-gray-900 border-b border-gray-700 text-center">
<div class="relative inline-block overflow-hidden rounded-full w-20 h-20 mx-auto mb-3 shadow-md bg-white p-1">
<img src="../assets/img/logo.png" alt="Logo" class="w-full h-full object-cover rounded-full relative z-10">
<div class="absolute inset-0 z-20 w-1/2 h-full bg-gradient-to-r from-transparent via-white/60 to-transparent -skew-x-12 animate-shine mix-blend-overlay"></div>
</div>
<h3 class="text-white text-lg font-bold tracking-wide mt-1">KSH-STAFF Admin</h3>
</div>
<nav class="flex-1 pt-6 pb-4 overflow-y-auto flex flex-col gap-1">
<a href="index.php" class="block px-6 py-3 hover:bg-gray-700 hover:text-white transition duration-200">
<i class="fas fa-home w-6 text-center mr-2"></i> แดชบอร์ด
</a>
<a href="staff_list.php" class="block px-6 py-3 hover:bg-gray-700 hover:text-white transition duration-200">
<i class="fas fa-users w-6 text-center mr-2"></i> รายชื่อเจ้าหน้าที่
</a>
<?php if ($_SESSION['admin_role'] === 'superadmin'): ?>
<a href="manage_admins.php" class="block px-6 py-3 bg-blue-600 text-white font-medium transition duration-200 shadow-sm">
<i class="fas fa-crown w-6 text-center mr-2"></i> จัดการแอดมิน
</a>
<?php endif; ?>
<div class="mt-auto px-6 pt-4">
<a href="logout.php" class="flex items-center text-red-400 hover:text-red-300 hover:bg-red-400/10 py-2 px-3 rounded transition duration-200">
<i class="fas fa-sign-out-alt w-6 text-center mr-1"></i> ออกจากระบบ
</a>
</div>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 ml-64 flex flex-col min-h-screen">
<header class="bg-white px-8 py-5 border-b border-gray-200 flex justify-between items-center shadow-sm z-10 sticky top-0">
<h4 class="text-xl font-bold text-gray-700"><i class="fas fa-crown text-yellow-500 mr-2"></i> จัดการผู้ดูแลระบบ (Super Admin)</h4>
<div class="text-sm text-gray-500 font-medium px-4 py-1.5 bg-gray-100 rounded-full border border-gray-200 flex items-center gap-2">
ผู้ใช้งาน: <span class="text-blue-600 font-bold"><?= htmlspecialchars($current_admin['username'] ?? $_SESSION['admin_username'] ?? 'Admin') ?></span>
<span class="bg-gray-200 text-gray-600 px-2 py-0.5 rounded text-xs"><?= htmlspecialchars($current_admin['role'] ?? '') ?></span>
</div>
</header>
<div class="p-8 flex-1">
<?php if (!empty($msg)): ?>
<script>Swal.fire({icon: 'success', title: 'สำเร็จ', text: '<?= $msg ?>', timer: 2000, confirmButtonColor: '#2563eb'});</script>
<?php endif; ?>
<?php if (!empty($error)): ?>
<script>Swal.fire({icon: 'error', title: 'ผิดพลาด', text: '<?= $error ?>', confirmButtonColor: '#2563eb'});</script>
<?php endif; ?>
<div class="bg-white rounded-xl p-8 shadow-sm border border-gray-200 mb-8">
<h3 class="text-lg font-bold text-gray-800 border-b border-gray-100 pb-4 mb-6"><i class="fas fa-user-plus text-blue-500 mr-2"></i> เพิ่มผู้ดูแลระบบ (Admin)</h3>
<form method="post" class="flex flex-col md:flex-row gap-6 items-end">
<div class="flex-1 w-full">
<label class="block text-gray-700 text-sm font-medium mb-2">เลขบัตรประชาชน (CID)</label>
<input type="text" name="cid" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition" required pattern="\d{13}" placeholder="กรอกเลข 13 หลัก">
</div>
<div class="flex-1 w-full">
<label class="block text-gray-700 text-sm font-medium mb-2">ตั้งรหัสผ่านเริ่มต้น</label>
<input type="password" name="password" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition" required placeholder="รหัสผ่าน">
</div>
<div class="w-full md:w-auto">
<button type="submit" name="add_admin" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-6 rounded-lg transition duration-200 shadow-sm flex items-center justify-center gap-2">
<i class="fas fa-plus"></i> เพิ่มสิทธิ์
</button>
</div>
</form>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="p-6 border-b border-gray-200">
<h3 class="text-lg font-bold text-gray-800"><i class="fas fa-list text-gray-400 mr-2"></i> รายชื่อผู้ดูแลระบบในระบบ</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-gray-50 text-gray-600 text-sm uppercase tracking-wider">
<th class="px-6 py-4 font-semibold border-b border-gray-200">Username / CID</th>
<th class="px-6 py-4 font-semibold border-b border-gray-200">สิทธิ์ (Role)</th>
<th class="px-6 py-4 font-semibold border-b border-gray-200">สถานะ 2FA</th>
<th class="px-6 py-4 font-semibold border-b border-gray-200">จัดการ</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php foreach ($admins as $ad): ?>
<tr class="hover:bg-gray-50 transition duration-150">
<td class="px-6 py-4 text-gray-800 font-medium"><?= htmlspecialchars($ad['username']) ?></td>
<td class="px-6 py-4">
<?php if ($ad['role'] === 'superadmin'): ?>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold bg-red-100 text-red-700"><i class="fas fa-crown"></i> Super Admin</span>
<?php else: ?>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold bg-blue-100 text-blue-700"><i class="fas fa-user-shield"></i> Admin</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 text-sm">
<?= $ad['google2fa_secret'] ? '<span class="text-emerald-600 font-medium flex items-center gap-1.5"><i class="fas fa-check-circle"></i> ตั้งค่าแล้ว</span>' : '<span class="text-red-500 font-medium flex items-center gap-1.5"><i class="fas fa-times-circle"></i> ยังไม่ตั้งค่า</span>' ?>
</td>
<td class="px-6 py-4">
<?php if ($ad['role'] !== 'superadmin'): ?>
<div class="flex gap-2">
<form method="post" onsubmit="return confirm('ยืนยันการรีเซ็ต 2FA?');">
<input type="hidden" name="admin_id" value="<?= $ad['id'] ?>">
<button type="submit" name="reset_2fa" class="bg-amber-500 hover:bg-amber-600 text-white px-3 py-1.5 rounded text-sm font-medium transition flex items-center gap-1.5"><i class="fas fa-sync-alt"></i> รีเซ็ต 2FA</button>
</form>
<form method="post" onsubmit="return confirm('ยืนยันการลบสิทธิ์ Admin?');">
<input type="hidden" name="admin_id" value="<?= $ad['id'] ?>">
<button type="submit" name="delete_admin" class="bg-red-500 hover:bg-red-600 text-white px-3 py-1.5 rounded text-sm font-medium transition flex items-center gap-1.5"><i class="fas fa-trash-alt"></i> ลบสิทธิ์</button>
</form>
</div>
<?php else: ?>
<span class="text-gray-400 font-medium">-</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,159 @@
<?php
session_start();
require_once '../includes/db.php';
require_once '../includes/GoogleAuthenticator.php';
if (!isset($_SESSION['admin_logged_in'])) {
header("Location: login.php");
exit;
}
$stmt = $pdo->prepare("SELECT google2fa_secret FROM admin_users WHERE id = ?");
$stmt->execute([$_SESSION['admin_id']]);
$admin = $stmt->fetch();
// If already set up, redirect to dashboard
if (!empty($admin['google2fa_secret'])) {
header("Location: index.php");
exit;
}
$ga = new PHPGangsta_GoogleAuthenticator();
$error = '';
if (!isset($_SESSION['new_2fa_secret'])) {
$_SESSION['new_2fa_secret'] = $ga->createSecret();
}
$secret = $_SESSION['new_2fa_secret'];
$qrCodeUrl = $ga->getQRCodeGoogleUrl('KSH-STAFF-Admin', $secret);
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['verify_code'])) {
$code = $_POST['code'] ?? '';
if ($ga->verifyCode($secret, $code, 2)) {
// Save to DB
$update = $pdo->prepare("UPDATE admin_users SET google2fa_secret = ? WHERE id = ?");
$update->execute([$secret, $_SESSION['admin_id']]);
unset($_SESSION['new_2fa_secret']);
echo "<script>alert('ตั้งค่า Google Authenticator สำเร็จ'); window.location.href='index.php';</script>";
exit;
} else {
$error = "รหัสไม่ถูกต้อง กรุณาลองใหม่อีกครั้ง";
}
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ตั้งค่า Google Authenticator</title>
<link rel="icon" type="image/png" href="../assets/img/logo.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Sarabun', 'sans-serif'],
},
animation: {
shine: 'shine 3s ease-in-out infinite'
},
keyframes: {
shine: {
'0%': { left: '-150%' },
'20%': { left: '150%' },
'100%': { left: '150%' }
}
}
}
}
}
</script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center font-sans antialiased p-4">
<div class="bg-white rounded-xl shadow-lg p-8 w-full max-w-lg border border-gray-100 text-center">
<div class="relative inline-block overflow-hidden rounded-full w-20 h-20 mx-auto mb-4 shadow-md bg-white p-1">
<img src="../assets/img/logo.png" alt="Logo" class="w-full h-full object-cover rounded-full relative z-10">
<div class="absolute inset-0 z-20 w-1/2 h-full bg-gradient-to-r from-transparent via-white/60 to-transparent -skew-x-12 animate-shine mix-blend-overlay"></div>
</div>
<h3 class="text-blue-600 text-2xl font-bold mb-3">ตั้งค่าความปลอดภัย (2FA)</h3>
<p class="text-gray-600 text-sm mb-6">เนื่องจากเป็นการเข้าสู่ระบบครั้งแรก กรุณาตั้งค่า Google Authenticator เพื่อความปลอดภัยในการเข้าถึงข้อมูล</p>
<div class="flex justify-center mb-6">
<img src="<?= $qrCodeUrl ?>" alt="QR Code" class="rounded-lg border border-gray-200 p-3 shadow-sm bg-white">
</div>
<div class="bg-blue-50 text-blue-800 rounded-lg p-4 text-sm text-left mb-6 shadow-inner">
<ol class="list-decimal list-inside space-y-2 font-medium">
<li>โหลดแอป Google Authenticator</li>
<li>สแกน QR Code ด้านบน</li>
<li>นำรหัส 6 หลักที่ได้มากรอกด้านล่าง</li>
</ol>
</div>
<?php if (!empty($error)): ?>
<div class="text-red-500 text-sm mb-4 font-medium bg-red-50 py-2 rounded-lg"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="post" id="otp-form" class="space-y-6">
<div class="flex justify-center gap-2">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp2')" id="otp1" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition" autofocus>
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp3')" onkeydown="moveToPrev(event, this, 'otp1')" id="otp2" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp4')" onkeydown="moveToPrev(event, this, 'otp2')" id="otp3" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp5')" onkeydown="moveToPrev(event, this, 'otp3')" id="otp4" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="moveToNext(this, 'otp6')" onkeydown="moveToPrev(event, this, 'otp4')" id="otp5" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
<input type="text" maxlength="1" oninput="submitOtp()" onkeydown="moveToPrev(event, this, 'otp5')" id="otp6" class="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-4 focus:ring-blue-500/20 outline-none transition">
</div>
<input type="hidden" name="code" id="code2fa" required>
<button type="submit" name="verify_code" id="submit-btn" class="hidden">ยืนยันรหัสและบันทึก</button>
</form>
<div class="mt-8">
<a href="logout.php" class="text-red-500 hover:text-red-700 text-sm font-medium hover:underline transition">ออกจากระบบ (ตั้งค่าภายหลัง)</a>
</div>
</div>
<script>
function moveToNext(current, nextFieldId) {
if (current.value.length === 1) {
if (nextFieldId) document.getElementById(nextFieldId).focus();
}
}
function moveToPrev(e, current, prevFieldId) {
if (e.key === 'Backspace' && current.value === '') {
if (prevFieldId) document.getElementById(prevFieldId).focus();
}
}
function submitOtp() {
let code = '';
for (let i = 1; i <= 6; i++) {
code += document.getElementById('otp' + i).value;
}
document.getElementById('code2fa').value = code;
if (code.length === 6) {
document.getElementById('submit-btn').click();
}
}
// Handle pasting 6 digits at once
document.getElementById('otp1').addEventListener('paste', function(e) {
let paste = (e.clipboardData || window.clipboardData).getData('text');
if (paste.length === 6 && /^\d+$/.test(paste)) {
for (let i = 0; i < 6; i++) {
document.getElementById('otp' + (i+1)).value = paste[i];
}
submitOtp();
e.preventDefault();
}
});
</script>
</body>
</html>
@@ -0,0 +1,431 @@
<?php
session_start();
require_once '../includes/db.php';
if (!isset($_SESSION['admin_logged_in'])) {
header("Location: login.php");
exit;
}
$is_superadmin = ($_SESSION['admin_role'] ?? '') === 'superadmin';
$stmt = $pdo_bot->query("SELECT * FROM line_staff_register ORDER BY date DESC");
$staff_list = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Staff List - KSH-STAFF</title>
<link rel="icon" type="image/png" href="../assets/img/logo.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- DataTables & Buttons CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.1/css/buttons.dataTables.min.css">
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<!-- JS for Excel Export -->
<script src="https://cdn.datatables.net/buttons/2.4.1/js/dataTables.buttons.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.1/js/buttons.html5.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="../assets/css/datatables_custom.css?v=1.3">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Sarabun', 'sans-serif'],
},
animation: {
shine: 'shine 3s ease-in-out infinite'
},
keyframes: {
shine: {
'0%': { left: '-150%' },
'20%': { left: '150%' },
'100%': { left: '150%' }
}
}
}
}
}
</script>
<style>
/* เว้นระยะขอบซ้ายให้คอลัมน์ชื่อ-สกุล (คอลัมน์ที่ 3) */
#staffTable th:nth-child(3),
#staffTable td:nth-child(3) {
text-align: left !important;
padding-left: 30px !important;
}
/* DataTables length/filter Tailwind overrides */
.dataTables_length select { appearance: auto; background-color: #fff; }
</style>
</head>
<?php
$assessed_cids = [];
if (isset($pdo_assess) && $pdo_assess) {
$current_thai_year = date('Y') + 543;
try {
$stmt_assess = $pdo_assess->prepare("SELECT hr_cid FROM it_assessments WHERE assess_year = ?");
$stmt_assess->execute([(string)$current_thai_year]);
while ($r = $stmt_assess->fetch()) {
$assessed_cids[$r['hr_cid']] = true;
}
} catch (\PDOException $e) {
}
}
$active_count = 0;
$inactive_count = 0;
$assessed_count = 0;
$not_assessed_count = 0;
foreach ($staff_list as $row) {
$is_active = (($row['ksh_user'] ?? 'Y') === 'Y');
if ($is_active) {
$active_count++;
} else {
$inactive_count++;
}
if (isset($assessed_cids[$row['cid']])) {
$assessed_count++;
} else {
// นับคนที่ยังไม่ประเมิน เฉพาะคนที่ยังมีสถานะ "ปฏิบัติงาน" เท่านั้น
if ($is_active) {
$not_assessed_count++;
}
}
}
$total_count = count($staff_list);
// Add $current_admin logic
$stmt_admin = $pdo->prepare("SELECT username, role FROM admin_users WHERE id = ?");
$stmt_admin->execute([$_SESSION['admin_id'] ?? 0]);
$current_admin = $stmt_admin->fetch();
?>
<body class="bg-gray-100 font-sans antialiased text-gray-800 flex min-h-screen">
<!-- Sidebar -->
<aside class="w-64 bg-gray-800 text-gray-300 flex flex-col fixed top-0 left-0 h-screen shadow-lg z-20">
<div class="px-6 py-5 bg-gray-900 border-b border-gray-700 text-center">
<div class="relative inline-block overflow-hidden rounded-full w-20 h-20 mx-auto mb-3 shadow-md bg-white p-1">
<img src="../assets/img/logo.png" alt="Logo" class="w-full h-full object-cover rounded-full relative z-10">
<div class="absolute inset-0 z-20 w-1/2 h-full bg-gradient-to-r from-transparent via-white/60 to-transparent -skew-x-12 animate-shine mix-blend-overlay"></div>
</div>
<h3 class="text-white text-lg font-bold tracking-wide mt-1">KSH-STAFF Admin</h3>
</div>
<nav class="flex-1 pt-6 pb-4 overflow-y-auto flex flex-col gap-1">
<a href="index.php" class="block px-6 py-3 hover:bg-gray-700 hover:text-white transition duration-200">
<i class="fas fa-home w-6 text-center mr-2"></i> แดชบอร์ด
</a>
<a href="staff_list.php" class="block px-6 py-3 bg-blue-600 text-white font-medium transition duration-200 shadow-sm">
<i class="fas fa-users w-6 text-center mr-2"></i> รายชื่อเจ้าหน้าที่
</a>
<?php if ($is_superadmin): ?>
<a href="manage_admins.php" class="block px-6 py-3 hover:bg-gray-700 hover:text-white transition duration-200">
<i class="fas fa-crown w-6 text-center mr-2"></i> จัดการแอดมิน
</a>
<?php endif; ?>
<div class="mt-auto px-6 pt-4">
<a href="logout.php" class="flex items-center text-red-400 hover:text-red-300 hover:bg-red-400/10 py-2 px-3 rounded transition duration-200">
<i class="fas fa-sign-out-alt w-6 text-center mr-1"></i> ออกจากระบบ
</a>
</div>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 ml-64 flex flex-col min-h-screen">
<header class="bg-white px-8 py-5 border-b border-gray-200 flex flex-wrap justify-between items-center shadow-sm z-10 sticky top-0 gap-4">
<div class="flex items-center gap-3">
<h4 class="text-xl font-bold text-gray-700"><i class="fas fa-users text-blue-500 mr-2"></i> รายชื่อเจ้าหน้าที่ลงทะเบียนแล้ว</h4>
</div>
<div class="flex flex-wrap items-center gap-3">
<span id="syncProgress" class="hidden font-bold text-blue-600 text-sm bg-blue-50 px-3 py-1.5 rounded-full"></span>
<button id="btnCheckDuplicate" onclick="checkDuplicateCID()" class="bg-amber-500 hover:bg-amber-600 text-white font-medium py-2 px-4 rounded-lg transition duration-200 shadow-sm flex items-center gap-2 text-sm">
<i class="fas fa-users-slash"></i> เช็ค CID ซ้ำ
</button>
<button id="btnBulkUpdate" onclick="startBulkUpdate()" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200 shadow-sm flex items-center gap-2 text-sm">
<i class="fas fa-sync-alt"></i> ตรวจสอบสถานะกับ HR
</button>
<div class="h-8 w-px bg-gray-300 mx-1 hidden md:block"></div>
<!-- User Profile -->
<div class="text-sm text-gray-500 font-medium px-4 py-1.5 bg-gray-100 rounded-full border border-gray-200 flex items-center gap-2">
ผู้ใช้งาน: <span class="text-blue-600 font-bold"><?= htmlspecialchars($current_admin['username'] ?? $_SESSION['admin_username'] ?? 'Admin') ?></span>
<span class="bg-gray-200 text-gray-600 px-2 py-0.5 rounded text-xs"><?= htmlspecialchars($current_admin['role'] ?? '') ?></span>
</div>
</div>
</header>
<div class="p-8 flex-1">
<!-- Summary Cards -->
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
<div onclick="$('#statusFilter').val('').trigger('change'); window.table.column(7).search('').draw();" class="bg-white rounded-xl shadow-sm border-l-4 border-blue-500 p-4 text-center cursor-pointer hover:-translate-y-1 hover:shadow-md transition duration-200">
<h3 class="text-2xl font-bold text-blue-600 mb-1"><?= $total_count ?></h3>
<span class="text-gray-500 text-xs font-medium">ทั้งหมด (คน)</span>
</div>
<div onclick="$('#statusFilter').val('ปฏิบัติงาน').trigger('change'); window.table.column(7).search('').draw();" class="bg-white rounded-xl shadow-sm border-l-4 border-emerald-500 p-4 text-center cursor-pointer hover:-translate-y-1 hover:shadow-md transition duration-200">
<h3 class="text-2xl font-bold text-emerald-600 mb-1"><?= $active_count ?></h3>
<span class="text-gray-500 text-xs font-medium">ปฏิบัติงาน</span>
</div>
<div onclick="$('#statusFilter').val('พ้นสภาพ').trigger('change'); window.table.column(7).search('').draw();" class="bg-white rounded-xl shadow-sm border-l-4 border-red-500 p-4 text-center cursor-pointer hover:-translate-y-1 hover:shadow-md transition duration-200">
<h3 class="text-2xl font-bold text-red-500 mb-1"><?= $inactive_count ?></h3>
<span class="text-gray-500 text-xs font-medium">พ้นสภาพ</span>
</div>
<div onclick="$('#statusFilter').val('').trigger('change'); window.table.column(7).search('ประเมินแล้ว').draw();" class="bg-white rounded-xl shadow-sm border-l-4 border-indigo-500 p-4 text-center cursor-pointer hover:-translate-y-1 hover:shadow-md transition duration-200">
<h3 class="text-2xl font-bold text-indigo-500 mb-1"><?= $assessed_count ?></h3>
<span class="text-gray-500 text-xs font-medium">ประเมินแล้ว</span>
</div>
<div onclick="$('#statusFilter').val('ปฏิบัติงาน').trigger('change'); window.table.column(7).search('ยังไม่ประเมิน').draw();" class="bg-white rounded-xl shadow-sm border-l-4 border-orange-400 p-4 text-center cursor-pointer hover:-translate-y-1 hover:shadow-md transition duration-200">
<h3 class="text-2xl font-bold text-orange-400 mb-1"><?= $not_assessed_count ?></h3>
<span class="text-gray-500 text-xs font-medium">ยังไม่ประเมิน</span>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<!-- Custom Filter Dropdown (Moved via JS into DOM later) -->
<div id="customFilterContainer" style="display: none;">
<select id="statusFilter" class="border border-gray-300 rounded-lg px-3 py-1.5 ml-3 outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent font-sans text-sm text-gray-700 bg-white">
<option value="">-- กรองสถานะทั้งหมด --</option>
<option value="ปฏิบัติงาน">ปฏิบัติงาน</option>
<option value="พ้นสภาพ">พ้นสภาพ</option>
</select>
</div>
<table id="staffTable" class="display" style="width:100%">
<thead>
<tr>
<th width="12%">CID</th>
<th width="8%">HN</th>
<th width="15%">ชื่อ - นามสกุล</th>
<th width="10%">เบอร์โทร</th>
<th width="10%">Line UID</th>
<th width="15%">วันที่สมัคร</th>
<th width="15%">สถานะทำงาน</th>
<th width="15%">การประเมิน (<?= date('Y') + 543 ?>)</th>
</tr>
</thead>
<tbody>
<?php foreach ($staff_list as $row):
$date_formatted = '-';
if (!empty($row['date'])) {
$d = date_create($row['date']);
if ($d) {
$year_be = (int)date_format($d, 'Y') + 543;
$date_formatted = date_format($d, 'd/m/') . $year_be;
}
}
$phone = $row['phone'];
if (strpos($phone, '+66') === 0) {
$phone = '0' . substr($phone, 3);
}
// จัดรูปแบบ xx-xxxx-xxxx
$phone_clean = preg_replace('/[^0-9]/', '', $phone);
if (strlen($phone_clean) === 10) {
$phone = substr($phone_clean, 0, 2) . '-' . substr($phone_clean, 2, 4) . '-' . substr($phone_clean, 6);
} else if (strlen($phone_clean) === 9) {
$phone = substr($phone_clean, 0, 2) . '-' . substr($phone_clean, 2, 3) . '-' . substr($phone_clean, 5);
}
$is_assessed = isset($assessed_cids[$row['cid']]);
?>
<tr>
<td><?= htmlspecialchars($row['cid']) ?></td>
<td><?= htmlspecialchars($row['hn']) ?></td>
<td><?= htmlspecialchars($row['fname'] . ' ' . $row['lname']) ?></td>
<td><?= htmlspecialchars($phone) ?></td>
<td>
<?php if (!empty($row['user_id'])): ?>
<span style="color:#198754; font-weight: 500; cursor: help;" title="<?= htmlspecialchars($row['user_id']) ?>">
<i class="fas fa-check-circle"></i> ผูกแล้ว
</span>
<?php else: ?>
<span style="color:#dc3545; font-weight: 500;">
<i class="fas fa-times-circle"></i> ยังไม่ผูก
</span>
<?php endif; ?>
</td>
<td><?= $date_formatted ?></td>
<td id="status-td-<?= htmlspecialchars($row['cid']) ?>">
<?php if (($row['ksh_user'] ?? 'Y') === 'Y'): ?>
<span style="color:#198754; font-weight: 500;"><i class="fas fa-briefcase"></i> ปฏิบัติงาน</span>
<?php else: ?>
<span style="color:#dc3545; font-weight: 500;"><i class="fas fa-user-times"></i> พ้นสภาพ</span>
<?php endif; ?>
</td>
<td>
<?php if ($is_assessed): ?>
<span class="bg-indigo-100 text-indigo-700 px-3 py-1 rounded-full text-xs font-bold border border-indigo-200">
<i class="fas fa-clipboard-check mr-1"></i> ประเมินแล้ว
</span>
<?php else: ?>
<span class="bg-orange-100 text-orange-700 px-3 py-1 rounded-full text-xs font-bold border border-orange-200">
<i class="fas fa-exclamation-circle mr-1"></i> ยังไม่ประเมิน
</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
// DataTables Initialization
$(document).ready(function() {
window.table = $('#staffTable').DataTable({
"columnDefs": [
{ "className": "dt-center", "targets": "_all" },
{ "className": "dt-left", "targets": 2 }
],
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json",
"search": "",
"searchPlaceholder": "ค้นหาด้วยชื่อ, CID, เบอร์โทร..."
},
"dom": '<"dt-top-row"<"dt-top-left"lB>f>rt<"dt-bottom-row"ip>',
"buttons": [
{
extend: 'excelHtml5',
text: '<i class="fas fa-file-excel"></i> ส่งออก Excel',
title: 'รายชื่อเจ้าหน้าที่ลงทะเบียน Line KSH-STAFF',
exportOptions: {
columns: [0, 1, 2, 3, 4, 5, 6, 7]
}
}
],
"pageLength": 10,
"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "ทั้งหมด"]]
});
// Append custom filter dropdown next to export button
$('#customFilterContainer').contents().appendTo('.dt-top-left');
// Handle Filter Change
$('#statusFilter').on('change', function() {
var val = $(this).val();
window.table.column(6).search(val).draw();
});
});
// Duplicate CID Check Logic
function checkDuplicateCID() {
const btn = document.getElementById('btnCheckDuplicate');
const originalHtml = btn.innerHTML;
Swal.fire({
title: 'ยืนยันการเคลียร์ข้อมูล',
text: "ระบบจะทำการลบข้อมูล CID ที่ซ้ำกันออก โดยเก็บเฉพาะรายการที่ลงทะเบียนล่าสุดไว้เท่านั้น ต้องการดำเนินการหรือไม่?",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#6c757d',
confirmButtonText: 'ลบข้อมูลซ้ำ',
cancelButtonText: 'ยกเลิก'
}).then((result) => {
if (result.isConfirmed) {
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> กำลังตรวจสอบ...';
btn.disabled = true;
$.post('ajax_remove_duplicate_cid.php', {}, function(res) {
btn.innerHTML = originalHtml;
btn.disabled = false;
try {
const data = JSON.parse(res);
if (data.success) {
Swal.fire({
icon: 'success',
title: 'เรียบร้อย!',
text: data.message,
confirmButtonColor: '#007bff'
}).then(() => {
if(data.deleted_count > 0) location.reload();
});
} else {
Swal.fire('ผิดพลาด', data.message, 'error');
}
} catch(e) {
Swal.fire('ผิดพลาด', 'ระบบเซิร์ฟเวอร์ขัดข้อง', 'error');
}
});
}
});
}
const staffCIDs = [
<?php foreach ($staff_list as $row): ?>
"<?= htmlspecialchars($row['cid']) ?>",
<?php endforeach; ?>
];
async function startBulkUpdate() {
if(!confirm("ระบบจะทำการดึงข้อมูลสถานะล่าสุดจากฐานข้อมูล HR มาอัปเดตเจ้าหน้าที่ทั้งหมด " + staffCIDs.length + " รายการ\n\nต้องการดำเนินการต่อหรือไม่?")) {
return;
}
$('#btnBulkUpdate').prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> กำลังตรวจสอบ...');
$('#syncProgress').show().text(`ตรวจสอบแล้ว 0 / ${staffCIDs.length}`);
let updatedCount = 0;
for (let cid of staffCIDs) {
// If row is visible on current page, show loading spinner
const td = document.getElementById('status-td-' + cid);
if(td) {
td.innerHTML = '<span style="color:#007bff; font-weight: 500;"><i class="fas fa-spinner fa-spin"></i> กำลังตรวจสอบ...</span>';
}
try {
const res = await $.post('ajax_update_hr_status.php', { cid: cid });
const data = JSON.parse(res);
if (data.success && td) {
if (data.new_status === 'Y') {
td.innerHTML = '<span style="color:#198754; font-weight: 500;"><i class="fas fa-briefcase"></i> ปฏิบัติงาน</span>';
} else {
td.innerHTML = '<span style="color:#dc3545; font-weight: 500;"><i class="fas fa-user-times"></i> พ้นสภาพ</span>';
}
} else if (td) {
td.innerHTML = '<span style="color:#dc3545; font-weight: 500;"><i class="fas fa-exclamation-triangle"></i> ขัดข้อง</span>';
}
} catch (e) {
if(td) td.innerHTML = '<span style="color:#dc3545; font-weight: 500;"><i class="fas fa-exclamation-triangle"></i> ขัดข้อง</span>';
}
updatedCount++;
$('#syncProgress').text(`ตรวจสอบแล้ว ${updatedCount} / ${staffCIDs.length}`);
}
$('#btnBulkUpdate').prop('disabled', false).html('<i class="fas fa-sync-alt"></i> ตรวจสอบสถานะกับ HR (ทั้งหมด)');
$('#syncProgress').text(`เสร็จสิ้น! อัปเดตแล้ว ${updatedCount} รายการ`).delay(3000).fadeOut();
Swal.fire({
icon: 'success',
title: 'เสร็จสิ้น!',
text: 'อัปเดตสถานะเจ้าหน้าที่ทั้งหมดเรียบร้อยแล้ว',
confirmButtonColor: '#007bff'
}).then(() => {
// Reload page to refresh DataTable internal cache and formatting
location.reload();
});
}
</script>
</body>
</html>
@@ -0,0 +1,56 @@
<?php
require_once '../includes/db.php';
try {
// Create admin_users table
$pdo->exec("CREATE TABLE IF NOT EXISTS admin_users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
cid VARCHAR(13) NULL,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'admin',
google2fa_secret VARCHAR(255) NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)");
// Ensure columns exist (in case of old table structure)
try { $pdo->exec("ALTER TABLE admin_users ADD COLUMN cid VARCHAR(13) NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE admin_users ADD COLUMN role VARCHAR(20) DEFAULT 'admin'"); } catch (Exception $e) {}
// Force update superadmin role just in case
$pdo->exec("UPDATE admin_users SET role = 'superadmin' WHERE username = 'superadmin'");
// Create settings table
$pdo->exec("CREATE TABLE IF NOT EXISTS settings (
setting_key VARCHAR(50) PRIMARY KEY,
setting_value VARCHAR(255)
)");
$pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES ('require_assessment', '0')");
// Check if superadmin exists
$stmt = $pdo->prepare("SELECT id FROM admin_users WHERE username = 'superadmin'");
$stmt->execute();
if (!$stmt->fetch()) {
$defaultPass = password_hash('superadmin123', PASSWORD_DEFAULT);
$pdo->exec("INSERT INTO admin_users (username, password_hash, role) VALUES ('superadmin', '$defaultPass', 'superadmin')");
echo "✅ สร้างบัญชี Super Admin สำเร็จ (Username: superadmin / Password: superadmin123)<br>";
} else {
echo "️ บัญชี Super Admin มีอยู่แล้ว<br>";
}
echo "✅ ตาราง admin_users อัปเดตเรียบร้อย<br>";
// Debug: Show all users
echo "<hr><h3>ตรวจสอบรายชื่อ Admin ในฐานข้อมูล:</h3>";
$stmt = $pdo->query("SELECT id, username, role FROM admin_users");
while ($row = $stmt->fetch()) {
echo "ID: {$row['id']} | Username: {$row['username']} | Role: {$row['role']}<br>";
}
echo "<br><a href='login.php'>ไปที่หน้าเข้าสู่ระบบ</a>";
} catch (\PDOException $e) {
die("Error updating database: " . $e->getMessage());
}
?>