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

1659 lines
137 KiB
PHP

<?php
// views/admin_settings.php
require_once("../core/auth.php");
require_once("../config/db.php");
require_once("../core/utils.php");
require_once("../core/settings.php");
require_once("../inc/rfc6238.php");
// ตรวจสอบสิทธิ์ผู้ดูแลระบบ
$current_page = basename($_SERVER['PHP_SELF']);
$current_user = $_SESSION['account'] ?? 'Unknown';
if (!function_exists('is_admin') || !is_admin($current_user)) {
die("<div style='padding:2rem;text-align:center;font-family:sans-serif;'><h2>Access Denied</h2><p>คุณไม่มีสิทธิ์เข้าถึงหน้านี้</p><a href='dashboard.php'>กลับหน้าหลัก</a></div>");
}
$tab = $_GET['tab'] ?? 'general';
$success_msg = '';
$show_2fa_modal = false;
$modal_data = [];
// จัดการ POST Action ต่างๆ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
die("<div style='padding:2rem;text-align:center;font-family:sans-serif;'><h2>CSRF Token Validation Failed</h2><p>การยืนยันตัวตนล้มเหลว กรุณาโหลดหน้าเว็บใหม่</p><a href='admin_settings.php'>โหลดใหม่</a></div>");
}
$action = $_POST['action'];
$target_user = $_POST['target_user'] ?? '';
if (!empty($target_user)) {
if ($action === 'reset_2fa') {
$stmt = $conn2->prepare("DELETE FROM sys_user_2fa WHERE loginname = ?");
if ($stmt) {
$stmt->bind_param("s", $target_user);
if ($stmt->execute()) {
system_log($conn2, $current_user, 'RESET_2FA', ['target_user' => $target_user]);
$success_msg = "รีเซ็ต 2FA สำหรับผู้ใช้ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว ระบบจะบังคับให้แสกน QR Code ใหม่เมื่อล็อกอินครั้งถัดไป";
}
$stmt->close();
}
} elseif ($action === 'generate_2fa') {
$target_name = $_POST['target_name'] ?? $target_user;
$new_secretkey = TokenAuth6238::generateRandomClue();
$qr_url = TokenAuth6238::getBarCodeUrl($target_name, '', $new_secretkey, 'HOSxP-WebService');
$show_2fa_modal = true;
$modal_data = [
'username' => $target_user,
'name' => $target_name,
'secret' => $new_secretkey,
'qr' => $qr_url
];
} elseif ($action === 'save_2fa') {
$secretkey = $_POST['secretkey'] ?? '';
if (!empty($secretkey)) {
$stmt = $conn2->prepare("INSERT INTO sys_user_2fa (loginname, secret_key) VALUES (?, ?) ON DUPLICATE KEY UPDATE secret_key = ?");
if ($stmt) {
$stmt->bind_param("sss", $target_user, $secretkey, $secretkey);
if ($stmt->execute()) {
system_log($conn2, $current_user, 'SETUP_2FA', ['target_user' => $target_user]);
$success_msg = "ตั้งค่า 2FA สำหรับ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
}
$stmt->close();
}
}
} elseif ($action === 'grant_admin') {
$stmt = $conn2->prepare("INSERT IGNORE INTO sys_admins (username, granted_by) VALUES (?, ?)");
if ($stmt) {
$stmt->bind_param("ss", $target_user, $current_user);
if ($stmt->execute()) {
system_log($conn2, $current_user, 'GRANT_ADMIN', ['target_user' => $target_user]);
$success_msg = "มอบสิทธิ์ผู้ดูแลระบบให้ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
}
$stmt->close();
}
} elseif ($action === 'revoke_admin') {
$stmt = $conn2->prepare("DELETE FROM sys_admins WHERE username = ?");
if ($stmt) {
$stmt->bind_param("s", $target_user);
if ($stmt->execute()) {
system_log($conn2, $current_user, 'REVOKE_ADMIN', ['target_user' => $target_user]);
$success_msg = "ปลดสิทธิ์ผู้ดูแลระบบของ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
}
$stmt->close();
}
} elseif ($action === 'grant_search_name') {
$stmt = $conn2->prepare("INSERT INTO sys_user_permissions (username, can_search_name, updated_by) VALUES (?, 1, ?) ON DUPLICATE KEY UPDATE can_search_name = 1, updated_by = ?");
if ($stmt) {
$stmt->bind_param("sss", $target_user, $current_user, $current_user);
if ($stmt->execute()) {
system_log($conn2, $current_user, 'GRANT_SEARCH_NAME', ['target_user' => $target_user]);
$success_msg = "เปิดสิทธิ์ค้นหาด้วยชื่อ-สกุล ให้ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
}
$stmt->close();
}
} elseif ($action === 'revoke_search_name') {
$stmt = $conn2->prepare("UPDATE sys_user_permissions SET can_search_name = 0, updated_by = ? WHERE username = ?");
if ($stmt) {
$stmt->bind_param("ss", $current_user, $target_user);
if ($stmt->execute()) {
system_log($conn2, $current_user, 'REVOKE_SEARCH_NAME', ['target_user' => $target_user]);
$success_msg = "ปิดสิทธิ์ค้นหาด้วยชื่อ-สกุล ของ <strong>" . htmlspecialchars($target_user) . "</strong> เรียบร้อยแล้ว";
}
$stmt->close();
}
} elseif ($action === 'unblock_ip') {
$target_ip = $target_user; // Repurposing target_user input for IP
if (!empty($target_ip)) {
require_once("../core/security.php");
clear_failed_logins($conn2, $target_ip);
system_log($conn2, $current_user, 'UNBLOCK_IP', ['target_ip' => $target_ip]);
$success_msg = "ปลดบล็อก IP <strong>" . htmlspecialchars($target_ip) . "</strong> เรียบร้อยแล้ว";
}
}
} elseif ($action === 'save_general') {
if (isset($_POST['settings']) && is_array($_POST['settings'])) {
// Decode base64 SQL fields to bypass WAF/Cloudflare
$sql_keys = [
'vaccine_sql', 'ncd_sql',
'dashboard_dm_all', 'dashboard_dm_hba1c',
'dashboard_dm_1', 'dashboard_dm_2', 'dashboard_dm_3', 'dashboard_dm_4', 'dashboard_dm_5', 'dashboard_dm_eye',
'dashboard_ht_all', 'dashboard_ht_1', 'dashboard_ht_2',
'dashboard_dlp_all',
'dashboard_ckd_all', 'dashboard_ckd_1'
];
foreach ($sql_keys as $k) {
if (isset($_POST['settings'][$k.'_base64'])) {
$_POST['settings'][$k] = base64_decode(strrev($_POST['settings'][$k.'_base64']));
unset($_POST['settings'][$k.'_base64']);
}
}
save_app_settings($_POST['settings']);
$updated_keys = array_keys($_POST['settings']);
$details_msg = "อัปเดตการตั้งค่าจำนวน " . count($updated_keys) . " รายการ (" . implode(', ', $updated_keys) . ")";
system_log($conn2, $current_user, 'UPDATE_SETTINGS', ['status' => 'success', 'details' => $details_msg]);
$success_msg = "บันทึกการตั้งค่าเรียบร้อยแล้ว";
$tab = $_POST['current_tab'] ?? 'general';
}
} elseif ($action === 'add_changelog') {
$r_date = $_POST['release_date'] ?? date('Y-m-d');
$r_ver = $_POST['version'] ?? '';
$r_desc = $_POST['description'] ?? '';
if (!empty($r_ver) && !empty($r_desc)) {
if (add_changelog($r_date, $r_ver, $r_desc)) {
$success_msg = "เพิ่มประวัติเวอร์ชั่น <strong>" . htmlspecialchars($r_ver) . "</strong> เรียบร้อยแล้ว";
}
}
$tab = $_POST['current_tab'] ?? $_GET['tab'] ?? 'general';
} elseif ($action === 'delete_changelog') {
$log_id = intval($_POST['log_id'] ?? 0);
if ($log_id > 0) {
if (delete_changelog($log_id)) {
$success_msg = "ลบประวัติการอัปเดตเรียบร้อยแล้ว";
}
}
$tab = $_POST['current_tab'] ?? $_GET['tab'] ?? 'general';
} elseif (strpos($action, 'test_notification_') === 0) {
require_once("../core/security.php");
$target = str_replace('test_notification_', '', $action);
$results = send_test_notification($target);
$msg_parts = [];
if (isset($results['line'])) {
$msg_parts[] = "Line Notify: " . ($results['line'] ? "✅ สำเร็จ" : "❌ ล้มเหลว");
}
if (isset($results['telegram'])) {
$msg_parts[] = "Telegram: " . ($results['telegram'] ? "✅ สำเร็จ" : "❌ ล้มเหลว");
}
if (empty($msg_parts)) {
$success_msg = "ยังไม่ได้เปิดใช้งานการแจ้งเตือนนี้ หรือตั้งค่าไม่ครบ โปรดตั้งค่าแล้วบันทึกก่อนทดสอบ";
} else {
$success_msg = "ผลการทดสอบแจ้งเตือน: " . implode(" | ", $msg_parts);
}
$tab = $_POST['current_tab'] ?? $_GET['tab'] ?? 'general';
}
}
// โหลดข้อมูลเจ้าหน้าที่ (Tab 1)
$users_list = [];
$admin_usernames = [];
$search_name_usernames = [];
if ($tab === '2fa') {
// โหลดรายชื่อแอดมินและสิทธิ์ค้นหาทั้งหมดมาก่อน
$res_admins = $conn2->query("SELECT username FROM sys_admins");
if ($res_admins) {
while ($r = $res_admins->fetch_assoc()) {
$admin_usernames[] = $r['username'];
}
}
$res_search = $conn2->query("SELECT username FROM sys_user_permissions WHERE can_search_name = 1");
if ($res_search) {
while ($r = $res_search->fetch_assoc()) {
$search_name_usernames[] = $r['username'];
}
}
// 1. ตรวจสอบ/สร้างตาราง sys_user_2fa ใน conn2
$conn2->query("CREATE TABLE IF NOT EXISTS sys_user_2fa (
loginname VARCHAR(255) PRIMARY KEY,
secret_key VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
// 2. ดึงข้อมูล 2FA จาก conn2
$user_2fa_keys = [];
$res_2fa = $conn2->query("SELECT loginname, secret_key FROM sys_user_2fa");
if ($res_2fa) {
while ($r = $res_2fa->fetch_assoc()) {
$user_2fa_keys[$r['loginname']] = $r['secret_key'];
}
}
// 3. (Optional Migration) ตรวจสอบและย้ายข้อมูลจาก opduser -> sys_user_2fa ถ้ายังว่างอยู่
if (empty($user_2fa_keys)) {
try {
$check_col = $conn1->query("SHOW COLUMNS FROM opduser LIKE 'two_fa_secret_key_samui'");
if ($check_col && $check_col->num_rows > 0) {
$mig = $conn1->query("SELECT loginname, two_fa_secret_key_samui FROM opduser WHERE two_fa_secret_key_samui IS NOT NULL AND two_fa_secret_key_samui != ''");
if ($mig && $mig->num_rows > 0) {
$stmt_ins = $conn2->prepare("INSERT IGNORE INTO sys_user_2fa (loginname, secret_key) VALUES (?, ?)");
while ($m_row = $mig->fetch_assoc()) {
$stmt_ins->bind_param("ss", $m_row['loginname'], $m_row['two_fa_secret_key_samui']);
$stmt_ins->execute();
$user_2fa_keys[$m_row['loginname']] = $m_row['two_fa_secret_key_samui']; // update local array
}
$stmt_ins->close();
}
}
} catch (Exception $e) { }
}
// กำหนดจำนวนข้อมูลต่อหน้า หรือดึงมาหมดถ้าไม่เยอะ (ที่นี่ดึงมาทั้งหมดก่อน)
$res = $conn1->query("SELECT loginname, name, account_disable FROM opduser ORDER BY name ASC");
if ($res) {
while ($row = $res->fetch_assoc()) {
$row['is_admin'] = in_array($row['loginname'], $admin_usernames);
$row['can_search_name'] = in_array($row['loginname'], $search_name_usernames);
$row['two_fa_secret_key_samui'] = $user_2fa_keys[$row['loginname']] ?? '';
$users_list[] = $row;
}
}
}
// โหลดข้อมูล Banned IPs (Tab 3)
$banned_ips = [];
if ($tab === 'security') {
$conn2->query("DELETE FROM sys_login_attempts WHERE attempt_time < (NOW() - INTERVAL 15 MINUTE)");
$res = $conn2->query("
SELECT ip_address, COUNT(*) as fail_count, MAX(attempt_time) as last_attempt, GROUP_CONCAT(DISTINCT username) as target_users
FROM sys_login_attempts
GROUP BY ip_address
HAVING fail_count >= 5
ORDER BY last_attempt DESC
");
if ($res) {
while ($row = $res->fetch_assoc()) {
$banned_ips[] = $row;
}
}
}
// โหลดข้อมูล Logs (Tab 2)
$logs_list = [];
if ($tab === 'logs') {
$filter_search = $_GET['search'] ?? '';
$filter_action = $_GET['action'] ?? '';
$filter_start = $_GET['start_date'] ?? date('Y-m-d');
$filter_end = $_GET['end_date'] ?? date('Y-m-d');
$where_clauses = ["DATE(created_at) BETWEEN ? AND ?"];
$params = [$filter_start, $filter_end];
$types = "ss";
if (!empty($filter_search)) {
$where_clauses[] = "(username LIKE ? OR details LIKE ?)";
$search_param = "%{$filter_search}%";
$params[] = $search_param;
$params[] = $search_param;
$types .= "ss";
}
if (!empty($filter_action)) {
$where_clauses[] = "action = ?";
$params[] = $filter_action;
$types .= "s";
}
$where_sql = implode(' AND ', $where_clauses);
$sql_logs = "SELECT * FROM sys_audit_logs WHERE $where_sql ORDER BY created_at DESC LIMIT 500";
$stmt = $conn2->prepare($sql_logs);
if ($stmt) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
$res = $stmt->get_result();
while ($row = $res->fetch_assoc()) {
$logs_list[] = $row;
}
$stmt->close();
}
}
// ดึงรายการ Action ทั้งหมดที่มีในระบบเพื่อมาทำ Filter Dropdown
$actions_list = [];
$res_acts = $conn2->query("SELECT DISTINCT action FROM sys_audit_logs ORDER BY action ASC");
if ($res_acts) {
while($row = $res_acts->fetch_assoc()) {
$actions_list[] = $row['action'];
}
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<?php require_once("../components/head.php"); ?>
<!-- jQuery & DataTables -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- SQL Formatter -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql-formatter/12.2.3/sql-formatter.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<!-- DataTables Buttons for Excel Export -->
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.1/css/buttons.dataTables.min.css">
<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>
<style>
/* Custom CSS to make DataTables look good with Tailwind */
.dataTables_wrapper .dataTables_length select, .dataTables_wrapper .dataTables_filter input {
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 0.35rem 0.75rem;
outline: none;
margin-bottom: 1rem;
font-size: 0.875rem;
}
.dataTables_wrapper .dataTables_length select:focus, .dataTables_wrapper .dataTables_filter input:focus {
border-color: #10b981;
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
}
/* Excel Export Button Styling */
.dt-buttons .dt-button.btn-export-excel {
background: #10b981 !important;
color: white !important;
border: none !important;
border-radius: 0.5rem !important;
padding: 0.4rem 1rem !important;
font-size: 0.875rem !important;
font-family: 'Sarabun', sans-serif !important;
transition: all 0.2s !important;
display: inline-flex !important;
align-items: center !important;
gap: 0.5rem !important;
}
.dt-buttons .dt-button.btn-export-excel:hover {
background: #059669 !important;
}
.dataTables_wrapper .dt-buttons {
margin-bottom: 0 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
border-radius: 0.5rem !important;
padding: 0.25rem 0.75rem !important;
margin: 0 0.125rem !important;
border: 1px solid transparent !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
background: #10b981 !important;
color: white !important;
border: none !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover:not(.current) {
background: #f1f5f9 !important;
color: #334155 !important;
}
table.dataTable.no-footer {
border-bottom: 1px solid #f1f5f9;
}
table.dataTable thead th {
border-bottom: 1px solid #f1f5f9;
}
.dt-center {
text-align: center;
}
</style>
<script>
function generate2FA(username, name) {
document.getElementById('action_input').value = 'generate_2fa';
document.getElementById('target_user_input').value = username;
let nameInput = document.createElement('input');
nameInput.type = 'hidden';
nameInput.name = 'target_name';
nameInput.value = name;
document.getElementById('admin_action_form').appendChild(nameInput);
document.getElementById('admin_action_form').submit();
}
function confirmReset2FA(username, name) {
if (confirm(`คุณต้องการรีเซ็ต 2FA ของ ${name} (${username}) ใช่หรือไม่?\nการกระทำนี้จะลบ Secret Key เดิมทิ้งทันที`)) {
document.getElementById('action_input').value = 'reset_2fa';
document.getElementById('target_user_input').value = username;
document.getElementById('admin_action_form').submit();
}
}
function confirmGrantAdmin(username, name) {
if (confirm(`คุณต้องการมอบสิทธิ์ผู้ดูแลระบบให้ ${name} (${username}) ใช่หรือไม่?`)) {
document.getElementById('action_input').value = 'grant_admin';
document.getElementById('target_user_input').value = username;
document.getElementById('admin_action_form').submit();
}
}
function confirmRevokeAdmin(username, name) {
if (confirm(`คุณต้องการปลดสิทธิ์ผู้ดูแลระบบของ ${name} (${username}) ใช่หรือไม่?`)) {
document.getElementById('action_input').value = 'revoke_admin';
document.getElementById('target_user_input').value = username;
document.getElementById('admin_action_form').submit();
}
}
function confirmGrantSearchName(username, name) {
if (confirm(`คุณต้องการเปิดสิทธิ์ค้นหาด้วยชื่อ-สกุลให้ ${name} (${username}) ใช่หรือไม่?`)) {
document.getElementById('action_input').value = 'grant_search_name';
document.getElementById('target_user_input').value = username;
document.getElementById('admin_action_form').submit();
}
}
function confirmRevokeSearchName(username, name) {
if (confirm(`คุณต้องการปิดสิทธิ์ค้นหาด้วยชื่อ-สกุลของ ${name} (${username}) ใช่หรือไม่?`)) {
document.getElementById('action_input').value = 'revoke_search_name';
document.getElementById('target_user_input').value = username;
document.getElementById('admin_action_form').submit();
}
}
function confirmUnblockIP(ip) {
if (confirm(`คุณต้องการปลดบล็อก IP: ${ip} ใช่หรือไม่?`)) {
document.getElementById('action_input').value = 'unblock_ip';
document.getElementById('target_user_input').value = ip;
document.getElementById('admin_action_form').submit();
}
}
</script>
</head>
<body class="bg-blue-50 text-slate-800 font-sans antialiased min-h-screen flex">
<?php require_once("../components/layout_sidebar.php"); ?>
<main class="flex-1 flex flex-col min-w-0 h-screen overflow-hidden">
<?php require_once("../components/layout_topbar.php"); ?>
<div class="p-4 md:p-8 lg:p-10 flex-1 overflow-y-auto">
<div class="max-w-screen-2xl mx-auto animate-enter">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
<div>
<h1 class="text-3xl font-bold text-slate-800 tracking-tight">ตั้งค่าระบบ (Admin Settings)</h1>
<p class="text-slate-500 mt-1">จัดการผู้ใช้งาน, ระบบยืนยันตัวตน และตรวจสอบประวัติการใช้งาน</p>
</div>
</div>
<?php if(!empty($success_msg)): ?>
<div class="bg-blue-100 text-blue-800 px-6 py-4 rounded-xl mb-6 flex items-center gap-3 shadow-sm border border-blue-200">
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<span><?php echo $success_msg; ?></span>
</div>
<?php endif; ?>
<!-- Tabs Navigation -->
<div class="flex space-x-2 border-b border-slate-200 mb-6 overflow-x-auto">
<a href="?tab=general" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'general' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
การตั้งค่าทั่วไป (General Settings)
</a>
<a href="?tab=database" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'database' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
ตั้งค่าการแสดงผลเพิ่มเติม
</a>
<a href="?tab=ncd" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'ncd' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
ทะเบียนผู้ป่วย (NCD)
</a>
<a href="?tab=dashboard_ncd" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'dashboard_ncd' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
Dashboard NCD
</a>
<a href="?tab=2fa" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === '2fa' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
จัดการผู้ใช้งาน (Users & Roles)
</a>
<a href="?tab=security" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'security' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
รายการบล็อก (IP Bans)
</a>
<a href="?tab=logs" class="px-6 py-3 text-sm font-medium transition-colors border-b-2 whitespace-nowrap <?php echo $tab === 'logs' ? 'border-blue-500 text-blue-600' : 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'; ?>">
ประวัติการใช้งาน (System Logs)
</a>
</div>
<!-- Hidden form for admin actions -->
<form id="admin_action_form" method="POST" class="hidden">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" id="action_input" value="">
<input type="hidden" name="target_user" id="target_user_input" value="">
</form>
<?php if ($tab === 'general'): ?>
<!-- Tab: General Settings -->
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
<h3 class="text-lg font-bold text-slate-800">การตั้งค่าทั่วไป</h3>
<p class="text-sm text-slate-500">ปรับแต่งข้อความแสดงผลบนหน้าล็อกอินและแถบเมนู</p>
</div>
<div class="p-4 sm:p-6">
<?php $app_settings = get_app_settings(); ?>
<form method="POST" class="max-w-2xl space-y-6">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="save_general">
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
<div class="space-y-4">
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">หน้าล็อกอิน (Login Page)</h4>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อหลัก (Main Header)</label>
<input type="text" name="settings[login_header_main]" value="<?php echo htmlspecialchars($app_settings['login_header_main']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อรอง (Sub Header)</label>
<input type="text" name="settings[login_header_sub]" value="<?php echo htmlspecialchars($app_settings['login_header_sub']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
</div>
</div>
<div class="space-y-4 pt-2">
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">แถบเมนู (Sidebar)</h4>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อหลัก (Main Header)</label>
<input type="text" name="settings[sidebar_header_main]" value="<?php echo htmlspecialchars($app_settings['sidebar_header_main']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">หัวข้อรอง (Sub Header)</label>
<input type="text" name="settings[sidebar_header_sub]" value="<?php echo htmlspecialchars($app_settings['sidebar_header_sub']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
</div>
</div>
<div class="space-y-4 pt-2">
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">แถบด้านล่าง (Footer)</h4>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">ข้อความฝั่งซ้าย (Left Text)</label>
<input type="text" name="settings[footer_text]" value="<?php echo htmlspecialchars($app_settings['footer_text']); ?>" class="w-full form-input py-2 px-3 text-sm" required>
<p class="text-[11px] text-slate-500 mt-1">สามารถใส่ <code>{YEAR}</code> แทนปีปัจจุบันอัตโนมัติ และรองรับ HTML เบื้องต้นเช่น <code>&amp;copy;</code> (ข้อความ Powered By จะถูกบังคับแสดงขวามือเสมอ)</p>
</div>
</div>
<div class="space-y-4 pt-4 mt-4 border-t border-slate-100">
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">เงื่อนไขการค้นหาประวัติการรับบริการ</h4>
<div>
<label class="flex items-center gap-3 cursor-pointer p-3 border border-slate-200 rounded-lg bg-slate-50 hover:bg-slate-100 transition-colors">
<div class="relative">
<input type="hidden" name="settings[strict_search_enable]" value="0">
<input type="checkbox" name="settings[strict_search_enable]" value="1" class="sr-only peer" <?php echo ($app_settings['strict_search_enable'] == '1') ? 'checked' : ''; ?>>
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none 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-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div>
</div>
<div>
<span class="block text-sm font-medium text-slate-700">บังคับค้นหาแบบตรงตัว (Strict Search)</span>
<span class="block text-[11px] text-slate-500 mt-0.5">เมื่อเปิดใช้งาน การค้นหาประวัติการรับบริการด้วย HN, CID, หรือหมายเลขโทรศัพท์ จะต้องกรอกข้อมูลให้ตรงทุกตัวอักษรเท่านั้น (ยกเว้นการค้นหาด้วยชื่อ-สกุล)</span>
</div>
</label>
</div>
</div>
<div class="space-y-4 pt-4 mt-4 border-t border-slate-100">
<h4 class="font-semibold text-slate-700 border-b border-slate-100 pb-2">ระบบรักษาความปลอดภัย (Security)</h4>
<div>
<label class="flex items-center gap-3 cursor-pointer p-3 border border-slate-200 rounded-lg bg-slate-50 hover:bg-slate-100 transition-colors">
<div class="relative">
<input type="hidden" name="settings[auto_logout_enable]" value="0">
<input type="checkbox" id="auto_logout_toggle" name="settings[auto_logout_enable]" value="1" class="sr-only peer" <?php echo ($app_settings['auto_logout_enable'] == '1') ? 'checked' : ''; ?> onchange="document.getElementById('auto_logout_time_container').style.display = this.checked ? 'block' : 'none';">
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none 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-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div>
</div>
<div>
<span class="block text-sm font-medium text-slate-700">ออกจากระบบอัตโนมัติเมื่อไม่มีการใช้งาน (Auto Logout)</span>
<span class="block text-[11px] text-slate-500 mt-0.5">ป้องกันการถูกผู้อื่นใช้งานเครื่อง หากผู้ใช้ลืมออกจากระบบ</span>
</div>
</label>
</div>
<div id="auto_logout_time_container" style="display: <?php echo ($app_settings['auto_logout_enable'] == '1') ? 'block' : 'none'; ?>;">
<label class="block text-sm font-medium text-slate-700 mb-1">ระยะเวลาไม่มีการใช้งาน (นาที)</label>
<input type="number" name="settings[auto_logout_minutes]" value="<?php echo htmlspecialchars($app_settings['auto_logout_minutes']); ?>" min="1" max="1440" class="w-full form-input py-2 px-3 text-sm">
</div>
</div>
<div class="pt-4 mt-4 border-t border-slate-100">
<button type="submit" class="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm">
บันทึกการตั้งค่า
</button>
</div>
</form>
</div>
</div>
<!-- Tab: Notifications Settings -->
<div class="glass-card overflow-hidden animate-enter mt-6" style="animation-delay: 0.15s;">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
<svg class="w-5 h-5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path></svg>
การแจ้งเตือนเมื่อมีการแบน IP (Notifications)
</h3>
<p class="text-sm text-slate-500">ตั้งค่าการแจ้งเตือนเมื่อระบบระงับ IP เนื่องจากพยายามล็อกอินผิดพลาด</p>
</div>
</div>
<div class="p-4 sm:p-6">
<form method="POST" class="max-w-2xl space-y-6">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="save_general">
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
<div class="space-y-4">
<div class="p-5 bg-white rounded-xl border border-slate-200 shadow-sm">
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100">
<label for="line_notify_enable" class="flex items-center gap-3 cursor-pointer">
<input type="hidden" name="settings[line_notify_enable]" value="0">
<input type="checkbox" id="line_notify_enable" name="settings[line_notify_enable]" value="1" <?php echo ($app_settings['line_notify_enable'] == '1') ? 'checked' : ''; ?> class="w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer">
<span class="font-bold text-slate-800 flex items-center gap-2">
<svg class="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 24 24"><path d="M19.365 9.863c.349.0.63.285.63.631.0.345-.281.63-.63.63H17.61v1.138h1.755c.349.0.63.283.63.63.0.344-.281.629-.63.629h-2.386c-.345.0-.627-.285-.627-.629V8.142c0-.344.282-.629.627-.629h2.386c.349.0.63.285.63.629.0.344-.281.63-.63.63H17.61v1.091h1.755zM14.958 11.521c0 .344-.282.629-.628.629h-2.386c-.345.0-.627-.285-.627-.629V8.142c0-.344.282-.629.627-.629h2.386c.346.0.628.285.628.629.0.344-.282.63-.628.63H12.58v1.091h1.751c.345.0.628.285.628.63.0.345-.283.63-.628.63H12.58v1.138h1.751c.346.0.628.283.628.63zM10.153 12.15h-2.181V8.142c0-.344-.281-.629-.628-.629-.346.0-.629.285-.629.629v4.636c0 .344.283.629.629.629h2.809c.346.0.628-.285.628-.629.0-.345-.282-.628-.628-.628zM5.539 12.15H4.283V8.142c0-.344-.282-.629-.628-.629-.345.0-.628.285-.628.629v4.008H1.77c-.345.0-.628.285-.628.628.0.345.283.629.628.629h3.769c.346.0.628-.284.628-.629.0-.344-.282-.628-.628-.628z" transform="translate(1 5)"/></svg>
Line Notify
</span>
</label>
<button type="submit" name="action" value="test_notification_line" class="px-4 py-1.5 bg-green-50 text-green-700 text-sm font-bold rounded-lg hover:bg-green-100 transition-colors border border-green-200" title="ทดสอบส่งผ่าน Line Notify (กรุณาบันทึกการตั้งค่าก่อน)">
ทดสอบ Line
</button>
</div>
<div class="ml-8">
<label class="block text-sm font-medium text-slate-600 mb-1">Line Notify Token</label>
<input type="text" name="settings[line_notify_token]" value="<?php echo htmlspecialchars($app_settings['line_notify_token']); ?>" class="w-full form-input py-2 px-3 text-sm" placeholder="ใส่ Token ของ Line Notify">
</div>
</div>
<div class="p-5 bg-white rounded-xl border border-slate-200 shadow-sm mt-4">
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100">
<label for="telegram_notify_enable" class="flex items-center gap-3 cursor-pointer">
<input type="hidden" name="settings[telegram_notify_enable]" value="0">
<input type="checkbox" id="telegram_notify_enable" name="settings[telegram_notify_enable]" value="1" <?php echo ($app_settings['telegram_notify_enable'] == '1') ? 'checked' : ''; ?> class="w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer">
<span class="font-bold text-slate-800 flex items-center gap-2">
<svg class="w-5 h-5 text-sky-500" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69.01-.03.01-.14-.07-.18-.08-.05-.19-.02-.27.01-.12.04-1.96 1.25-5.54 3.69-.52.36-1 .53-1.42.52-.47-.01-1.37-.26-2.03-.48-.82-.27-1.47-.42-1.42-.88.03-.24.29-.48.79-.74 3.08-1.34 5.15-2.23 6.19-2.66 2.95-1.23 3.56-1.44 3.96-1.45.09.0.28.02.4.11.1.08.13.19.14.28-.01.07.01.21-.01.32z"/></svg>
Telegram
</span>
</label>
<button type="submit" name="action" value="test_notification_telegram" class="px-4 py-1.5 bg-sky-50 text-sky-700 text-sm font-bold rounded-lg hover:bg-sky-100 transition-colors border border-sky-200" title="ทดสอบส่งผ่าน Telegram (กรุณาบันทึกการตั้งค่าก่อน)">
ทดสอบ Telegram
</button>
</div>
<div class="ml-8 grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-600 mb-1">Bot Token</label>
<input type="text" name="settings[telegram_bot_token]" value="<?php echo htmlspecialchars($app_settings['telegram_bot_token']); ?>" class="w-full form-input py-2 px-3 text-sm" placeholder="ใส่ Bot Token">
</div>
<div>
<label class="block text-sm font-medium text-slate-600 mb-1">Chat ID</label>
<input type="text" name="settings[telegram_chat_id]" value="<?php echo htmlspecialchars($app_settings['telegram_chat_id']); ?>" class="w-full form-input py-2 px-3 text-sm" placeholder="ใส่ Chat ID ปลายทาง">
</div>
</div>
</div>
</div>
<div class="pt-4 flex flex-wrap gap-3">
<button type="submit" class="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm">
บันทึกการตั้งค่าการแจ้งเตือน
</button>
</div>
</form>
</div>
</div>
<div class="glass-card overflow-hidden animate-enter mt-6" style="animation-delay: 0.2s;">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
<svg class="w-5 h-5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"></path></svg>
ประกาศข่าวสาร (Version History)
</h3>
<p class="text-sm text-slate-500">บันทึกประวัติการอัปเดตและแจ้งเตือนเวอร์ชั่นใหม่</p>
</div>
</div>
<div class="p-4 sm:p-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Add Form -->
<div class="lg:col-span-1 bg-slate-50/50 p-5 rounded-xl border border-slate-100 h-fit">
<h4 class="font-semibold text-slate-700 mb-4 text-sm">เพิ่มประวัติใหม่</h4>
<form method="POST" class="space-y-4">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="add_changelog">
<div>
<label class="block text-xs font-medium text-slate-600 mb-1">วันที่ประกาศ</label>
<input type="date" name="release_date" value="<?php echo date('Y-m-d'); ?>" class="w-full form-input py-2 px-3 text-sm" required>
</div>
<div>
<label class="block text-xs font-medium text-slate-600 mb-1">เวอร์ชั่น (เช่น v2.9)</label>
<input type="text" name="version" class="w-full form-input py-2 px-3 text-sm" placeholder="HOSxP Web Service v..." required>
</div>
<div>
<label class="block text-xs font-medium text-slate-600 mb-1">รายละเอียด (Badge / Note)</label>
<input type="text" name="description" class="w-full form-input py-2 px-3 text-sm" placeholder="เพิ่มฟีเจอร์..." required>
</div>
<button type="submit" class="w-full py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm">
บันทึกประวัติ
</button>
</form>
</div>
<!-- List view -->
<div class="lg:col-span-2">
<div class="space-y-4">
<?php
$logs = get_changelogs();
if (empty($logs)):
?>
<div class="text-center py-8 text-slate-400">
<svg class="w-12 h-12 mx-auto mb-3 text-slate-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
<p>ยังไม่มีประวัติการอัปเดต</p>
</div>
<?php else: ?>
<?php foreach ($logs as $log):
$d = new DateTime($log['release_date']);
$thai_year = $d->format('Y') + 543;
$display_date = $d->format('d-m-') . $thai_year;
?>
<div class="relative bg-white border border-slate-100 rounded-2xl p-5 shadow-sm hover:shadow-md transition-all flex items-center justify-between group overflow-hidden">
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-amber-400 rounded-l-2xl"></div>
<div class="pl-2">
<div class="text-[11px] font-bold text-amber-500 mb-1 tracking-wide"><?php echo $display_date; ?></div>
<div class="flex items-center gap-3 flex-wrap">
<h4 class="text-base font-semibold text-slate-800"><?php echo htmlspecialchars($log['version']); ?></h4>
<?php if(!empty($log['description'])): ?>
<span class="px-3 py-1 bg-blue-50 text-blue-700 text-xs font-medium rounded-full border border-blue-100/50">
<?php echo htmlspecialchars($log['description']); ?>
</span>
<?php endif; ?>
</div>
</div>
<form method="POST" onsubmit="return confirm('ยืนยันการลบประวัตินี้?');">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="delete_changelog">
<input type="hidden" name="log_id" value="<?php echo $log['id']; ?>">
<button type="submit" class="p-2 text-slate-300 hover:text-rose-500 hover:bg-rose-50 rounded-lg transition-colors opacity-0 group-hover:opacity-100" title="ลบข้อมูล">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
</button>
</form>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($tab === 'database'): ?>
<!-- Tab: Database Settings -->
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
<h3 class="text-lg font-bold text-slate-800">ตั้งค่าการแสดงผลเพิ่มเติม</h3>
<p class="text-sm text-slate-500">ปรับแต่งคำสั่ง SQL และการเชื่อมต่อฐานข้อมูลสำหรับดึงข้อมูลมาแสดงผลในส่วนต่างๆ</p>
</div>
<div class="p-4 sm:p-6">
<?php $app_settings = get_app_settings(); ?>
<form method="POST" class="max-w-4xl space-y-6" onsubmit="return encodeSQL();">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="save_general">
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
<div class="space-y-6">
<!-- Vaccine System Section -->
<div class="bg-white border border-slate-200 rounded-xl p-5 sm:p-6 shadow-sm">
<div class="flex items-center gap-3 mb-4 border-b border-slate-100 pb-3">
<div class="w-10 h-10 rounded-lg bg-blue-50 text-blue-600 flex items-center justify-center">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 2 4 4"/><path d="m17 7 3-3"/><path d="M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"/><path d="m9 11 4 4"/><path d="m5 19-3 3"/><path d="m14 4 6 6"/></svg>
</div>
<div>
<h4 class="font-bold text-slate-800 text-lg">ระบบประวัติวัคซีน (Vaccination System)</h4>
<p class="text-xs text-slate-500">ดึงข้อมูลประวัติการรับวัคซีนจากฐานข้อมูล HIS</p>
</div>
</div>
<div>
<div class="flex items-center justify-between mb-2">
<label class="block text-sm font-medium text-slate-700">ชุดคำสั่ง SQL (SQL Query)</label>
<button type="button" onclick="formatSqlInput('vaccine_sql_input')" class="text-xs bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-3 py-1.5 rounded-md transition-colors border border-slate-200 hover:border-blue-300 flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path></svg>
จัดรูปแบบ SQL
</button>
</div>
<div class="bg-blue-50 text-blue-800 p-4 rounded-lg mb-4 text-[13px] border border-blue-100">
<div class="font-bold mb-2 flex items-center gap-1 text-blue-900">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
คำแนะนำและข้อบังคับในการเขียน SQL
</div>
<ul class="list-disc pl-5 space-y-1.5">
<li>กรุณาใช้คำว่า <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">'ระบุ_HN'</code> (มี Single Quote) แทนรหัส HN ของผู้ป่วยในเงื่อนไข WHERE เสมอ</li>
<li>ผลลัพธ์จะต้องตั้งชื่อคอลัมน์ (AS) ให้ตรงกับตัวแปรเหล่านี้:</li>
<ul class="list-circle pl-5 mt-2 space-y-1 text-blue-700">
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_group</code> = ชื่อกลุ่มของวัคซีน (ระบบใช้คอลัมน์นี้ในการแบ่งกลุ่มและแยกสี)</li>
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_name</code> = ชื่อวัคซีนที่ฉีด</li>
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_date</code> = วันที่รับวัคซีน (รูปแบบ YYYY-MM-DD)</li>
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_code</code> = รหัสวัคซีน (แสดงเป็นตัวเล็กๆ ข้างชื่อ)</li>
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_lot_no</code> = หมายเลข Lot (ไม่บังคับ)</li>
<li><code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">vaccine_place_name</code> = สถานที่รับวัคซีน (ไม่บังคับ)</li>
</ul>
</ul>
</div>
<input type="hidden" name="settings[vaccine_sql_base64]" id="vaccine_sql_base64">
<textarea id="vaccine_sql_input" rows="14" class="w-full form-input py-3 px-4 text-sm font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 shadow-inner" spellcheck="false"><?php echo htmlspecialchars($app_settings['vaccine_sql'] ?? ''); ?></textarea>
</div>
</div>
<!-- Placeholder for future modules -->
<!--
<div class="bg-white border border-slate-200 rounded-xl p-5 sm:p-6 shadow-sm border-dashed opacity-60">
<div class="flex items-center justify-center py-8 flex-col text-slate-400">
<svg class="w-8 h-8 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
<span class="text-sm font-medium">เพิ่มชุดคำสั่งใหม่ในอนาคต</span>
</div>
</div>
-->
</div>
<div class="pt-2">
<button type="submit" class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm inline-flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
บันทึกการตั้งค่า
</button>
</div>
</form>
<script>
function encodeSQL() {
var sqlInput = document.getElementById('vaccine_sql_input');
var b64Hidden = document.getElementById('vaccine_sql_base64');
if (sqlInput && b64Hidden) {
var b64 = btoa(unescape(encodeURIComponent(sqlInput.value)));
b64Hidden.value = b64.split('').reverse().join('');
sqlInput.name = ''; // Prevent raw SQL submission to bypass Cloudflare
}
return true;
}
</script>
</div>
</div>
<?php endif; ?>
<?php if ($tab === 'ncd'): ?>
<!-- Tab: NCD Settings -->
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
<h3 class="text-lg font-bold text-slate-800">ทะเบียนผู้ป่วย (NCD Registry)</h3>
<p class="text-sm text-slate-500">ปรับแต่งคำสั่ง SQL สำหรับดึงข้อมูลในระบบทะเบียนผู้ป่วย NCD</p>
</div>
<div class="p-4 sm:p-6">
<?php $app_settings = get_app_settings(); ?>
<form method="POST" class="max-w-4xl space-y-6" onsubmit="return encodeNCDSQL();">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="save_general">
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
<div class="space-y-6">
<div class="bg-white border border-slate-200 rounded-xl p-5 sm:p-6 shadow-sm">
<div class="flex items-center justify-between mb-4 border-b border-slate-100 pb-3">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-emerald-50 text-emerald-600 flex items-center justify-center">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
</div>
<div>
<h4 class="font-bold text-slate-800 text-lg">ชุดคำสั่ง SQL (NCD Registry SQL)</h4>
<p class="text-xs text-slate-500">ดึงข้อมูลทะเบียนผู้ป่วยเรื้อรังจาก HOSxP</p>
</div>
</div>
<button type="button" onclick="formatSqlInput('ncd_sql_input')" class="text-xs bg-slate-100 hover:bg-emerald-100 text-slate-600 hover:text-emerald-700 px-3 py-1.5 rounded-md transition-colors border border-slate-200 hover:border-emerald-300 flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path></svg>
จัดรูปแบบ SQL
</button>
</div>
<div>
<div class="bg-blue-50 text-blue-800 p-4 rounded-lg mb-4 text-[13px] border border-blue-100">
<div class="font-bold mb-2 flex items-center gap-1 text-blue-900">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
คำแนะนำและข้อบังคับในการเขียน SQL
</div>
<ul class="list-disc pl-5 space-y-1.5">
<li>ต้องมีตัวแทน (Placeholder) ชื่อ <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">{clinic_cond}</code> เพื่อให้ระบบนำเงื่อนไขคลินิกไปแทรก (เช่น `AND cl.clinic = '001'`)</li>
<li>ต้องมี `?` 2 ตำแหน่งสำหรับ bind param ค่าวันที่ (`vstdate between ? and ?`) ใน Prepared Statement</li>
<li>ต้องทำการตั้งชื่อ (AS) ให้ตรงกับที่ระบบนำไปแสดงผล (เช่น <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">pt_name</code>, <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">age_y</code>, <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">tmb_name</code>)</li>
</ul>
</div>
<input type="hidden" name="settings[ncd_sql_base64]" id="ncd_sql_base64">
<textarea id="ncd_sql_input" rows="18" class="w-full form-input py-3 px-4 text-sm font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 shadow-inner" spellcheck="false"><?php echo htmlspecialchars($app_settings['ncd_sql'] ?? ''); ?></textarea>
</div>
<div class="mt-8 pt-6 border-t border-slate-100">
<h4 class="font-bold text-slate-800 text-md mb-4 flex items-center gap-2">
<svg class="w-5 h-5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path></svg>
ตั้งค่ารหัสคลินิก (Clinic Codes Mapping)
</h4>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกเบาหวาน (DM)</label>
<input type="text" name="settings[ncd_clinic_dm_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_dm_code'] ?? '001'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกความดันฯ (HT)</label>
<input type="text" name="settings[ncd_clinic_ht_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_ht_code'] ?? '002'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกไขมันฯ (DLP)</label>
<input type="text" name="settings[ncd_clinic_dlp_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_dlp_code'] ?? '039'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">รหัสคลินิกโรคไต (CKD)</label>
<input type="text" name="settings[ncd_clinic_ckd_code]" value="<?php echo htmlspecialchars($app_settings['ncd_clinic_ckd_code'] ?? '051'); ?>" class="w-full form-input py-2 px-3 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500">
</div>
</div>
</div>
</div>
</div>
<div class="pt-2">
<button type="submit" class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm inline-flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
บันทึกการตั้งค่า
</button>
</div>
</form>
<script>
function encodeNCDSQL() {
var sqlInput = document.getElementById('ncd_sql_input');
var b64Hidden = document.getElementById('ncd_sql_base64');
if (sqlInput && b64Hidden) {
var b64 = btoa(unescape(encodeURIComponent(sqlInput.value)));
b64Hidden.value = b64.split('').reverse().join('');
sqlInput.name = ''; // Prevent raw SQL submission to bypass Cloudflare
}
return true;
}
</script>
</div>
</div>
<?php endif; ?>
<?php if ($tab === 'dashboard_ncd'): ?>
<!-- Tab: Dashboard NCD Settings -->
<div class="glass-card overflow-hidden animate-enter" style="animation-delay: 0.1s;">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
<h3 class="text-lg font-bold text-slate-800">หน้าปัด NCD (Dashboard NCD)</h3>
<p class="text-sm text-slate-500">ปรับแต่งคำสั่ง SQL สำหรับแสดงสถิติบน Dashboard (แทนที่วันที่ด้วย {ds1} และ {ds2})</p>
</div>
<div class="p-4 sm:p-6">
<?php $app_settings = get_app_settings(); ?>
<form method="POST" class="max-w-4xl space-y-6" onsubmit="return encodeDashboardSQL();">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="save_general">
<input type="hidden" name="current_tab" value="<?php echo htmlspecialchars($tab); ?>">
<div class="space-y-6">
<div class="bg-blue-50 text-blue-800 p-4 rounded-lg mb-4 text-[13px] border border-blue-100">
<div class="font-bold mb-2 flex items-center gap-1 text-blue-900">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
คำแนะนำและข้อบังคับในการเขียน SQL
</div>
<ul class="list-disc pl-5 space-y-1.5">
<li>กรุณาใช้ <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">'{ds1}'</code> และ <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">'{ds2}'</code> แทนค่าวันที่ในคำสั่ง SQL</li>
<li>ผลลัพธ์จะต้องตั้งชื่อคอลัมน์ (AS) ให้เป็น <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">total_patient</code> สำหรับยอดนับผู้ป่วยรวม ยกเว้นข้อ 7 (CKD) ให้มี <code class="bg-blue-100 px-1 py-0.5 rounded text-blue-900">stage</code> เพิ่มเติม</li>
</ul>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
<h4 class="font-bold text-blue-700 text-md">คลินิกเบาหวาน (DM)</h4>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยเบาหวานทั้งหมด</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_all_base64]" id="dashboard_dm_all_base64">
<textarea id="dashboard_dm_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_all'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจ HBA1C</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_hba1c_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_hba1c_base64]" id="dashboard_dm_hba1c_base64">
<textarea id="dashboard_dm_hba1c_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_hba1c'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">DM ไม่มีโรคร่วม HBA1C ครั้งสุดท้าย < 7</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_1_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_1_base64]" id="dashboard_dm_1_base64">
<textarea id="dashboard_dm_1_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_1'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">DM มีโรคร่วม HBA1C ครั้งสุดท้าย < 8</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_2_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_2_base64]" id="dashboard_dm_2_base64">
<textarea id="dashboard_dm_2_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_2'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจ LDL</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_3_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_3_base64]" id="dashboard_dm_3_base64">
<textarea id="dashboard_dm_3_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_3'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจไต</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_4_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_4_base64]" id="dashboard_dm_4_base64">
<textarea id="dashboard_dm_4_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_4'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจเท้า</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_5_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_5_base64]" id="dashboard_dm_5_base64">
<textarea id="dashboard_dm_5_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_5'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจตา</label>
<button type="button" onclick="formatSqlInput('dashboard_dm_eye_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dm_eye_base64]" id="dashboard_dm_eye_base64">
<textarea id="dashboard_dm_eye_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dm_eye'] ?? ''); ?></textarea>
</div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
<h4 class="font-bold text-emerald-700 text-md">คลินิกความดันฯ (HT)</h4>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยความดันโลหิตสูงทั้งหมด</label>
<button type="button" onclick="formatSqlInput('dashboard_ht_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_ht_all_base64]" id="dashboard_ht_all_base64">
<textarea id="dashboard_ht_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ht_all'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ความดัน 2 ครั้งสุดท้าย < 139/89</label>
<button type="button" onclick="formatSqlInput('dashboard_ht_1_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_ht_1_base64]" id="dashboard_ht_1_base64">
<textarea id="dashboard_ht_1_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ht_1'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ได้รับการตรวจไต</label>
<button type="button" onclick="formatSqlInput('dashboard_ht_2_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_ht_2_base64]" id="dashboard_ht_2_base64">
<textarea id="dashboard_ht_2_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ht_2'] ?? ''); ?></textarea>
</div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
<h4 class="font-bold text-purple-700 text-md">คลินิกไขมันในเลือดสูง (DLP)</h4>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยไขมันในเลือดสูงทั้งหมด</label>
<button type="button" onclick="formatSqlInput('dashboard_dlp_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_dlp_all_base64]" id="dashboard_dlp_all_base64">
<textarea id="dashboard_dlp_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_dlp_all'] ?? ''); ?></textarea>
</div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-5 shadow-sm space-y-4">
<h4 class="font-bold text-amber-700 text-md">คลินิกโรคไต (CKD)</h4>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">ผู้ป่วยโรคไตเรื้อรังทั้งหมด</label>
<button type="button" onclick="formatSqlInput('dashboard_ckd_all_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_ckd_all_base64]" id="dashboard_ckd_all_base64">
<textarea id="dashboard_ckd_all_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ckd_all'] ?? ''); ?></textarea>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-bold text-slate-700">จำนวนผู้ป่วยโรคไตแยกตาม Stage</label>
<button type="button" onclick="formatSqlInput('dashboard_ckd_1_input')" class="text-[10px] bg-slate-100 hover:bg-blue-100 text-slate-600 hover:text-blue-700 px-2 py-1 rounded transition-colors border border-slate-200 hover:border-blue-300">จัดรูปแบบ SQL</button>
</div>
<input type="hidden" name="settings[dashboard_ckd_1_base64]" id="dashboard_ckd_1_base64">
<textarea id="dashboard_ckd_1_input" rows="5" class="w-full form-input py-3 px-4 text-xs font-mono text-slate-700 bg-slate-50 border border-slate-200 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" spellcheck="false"><?php echo htmlspecialchars($app_settings['dashboard_ckd_1'] ?? ''); ?></textarea>
</div>
</div>
</div>
<div class="pt-2">
<button type="submit" class="px-6 py-2.5 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors shadow-sm inline-flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path></svg>
บันทึกการตั้งค่า
</button>
</div>
</form>
<script>
function encodeDashboardSQL() {
const inputs = [
'dashboard_dm_all', 'dashboard_dm_hba1c',
'dashboard_dm_1', 'dashboard_dm_2', 'dashboard_dm_3', 'dashboard_dm_4', 'dashboard_dm_5', 'dashboard_dm_eye',
'dashboard_ht_all', 'dashboard_ht_1', 'dashboard_ht_2',
'dashboard_dlp_all',
'dashboard_ckd_all', 'dashboard_ckd_1'
];
inputs.forEach(id => {
var sqlInput = document.getElementById(id + '_input');
var b64Hidden = document.getElementById(id + '_base64');
if (sqlInput && b64Hidden) {
var b64 = btoa(unescape(encodeURIComponent(sqlInput.value)));
b64Hidden.value = b64.split('').reverse().join('');
sqlInput.name = ''; // Prevent raw SQL submission
}
});
return true;
}
</script>
</div>
</div>
<?php endif; ?>
<?php if ($tab === '2fa'): ?>
<!-- Tab: 2FA Management -->
<div class="glass-card overflow-hidden">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h3 class="text-lg font-bold text-slate-800">รายชื่อเจ้าหน้าที่</h3>
<p class="text-sm text-slate-500">จัดการข้อมูลผู้ใช้งาน การยืนยันตัวตนแบบ 2 ขั้นตอน (2FA) และกำหนดสิทธิ์การเป็นแอดมิน</p>
</div>
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
<select id="statusFilter" class="form-input py-2 text-sm bg-white cursor-pointer">
<option value="active">เฉพาะที่ใช้งานอยู่ (Active)</option>
<option value="disabled">เฉพาะที่ถูกยกเลิก (Disabled)</option>
<option value="all">แสดงทั้งหมด</option>
</select>
<select id="roleFilter" class="form-input py-2 text-sm bg-white cursor-pointer">
<option value="">สิทธิ์: ทั้งหมด</option>
<option value="admin">เฉพาะผู้ดูแลระบบ (Admin)</option>
<option value="user">เฉพาะเจ้าหน้าที่ทั่วไป (User)</option>
<option value="search_name">เฉพาะผู้มีสิทธิ์ค้นหาชื่อ-สกุล</option>
</select>
<select id="twoFaFilter" class="form-input py-2 text-sm bg-white cursor-pointer">
<option value="">2FA: ทั้งหมด</option>
<option value="enabled">ทำ 2FA แล้ว</option>
<option value="disabled">ยังไม่ทำ 2FA</option>
</select>
<div id="exportBtnContainer" class="shrink-0 flex items-center"></div>
</div>
</div>
<div class="overflow-x-auto p-4 sm:p-6">
<table id="usersTable" class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-50/50 border-b border-slate-200 text-slate-500 text-sm">
<th class="px-6 py-4 font-semibold">Username</th>
<th class="px-6 py-4 font-semibold">ชื่อ - นามสกุล</th>
<th class="px-6 py-4 font-semibold">สิทธิ์ (Role)</th>
<th class="px-6 py-4 font-semibold text-center">ค้นหาชื่อ-สกุล</th>
<th class="px-6 py-4 font-semibold">สถานะ 2FA</th>
<th class="px-6 py-4 font-semibold text-right">การจัดการ</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
<?php foreach($users_list as $user): ?>
<?php
$has_2fa = !empty($user['two_fa_secret_key_samui']);
$is_user_admin = $user['is_admin'];
$can_search_name = $user['can_search_name'];
?>
<tr class="hover:bg-slate-50/50 transition-colors" data-status="<?php echo ($user['account_disable'] === 'Y') ? 'disabled' : 'active'; ?>">
<td class="px-6 py-4 font-medium text-slate-800">
<?php echo htmlspecialchars($user['loginname']); ?>
<?php if($user['account_disable'] === 'Y'): ?>
<span class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-[10px] font-medium bg-red-100 text-red-600 border border-red-200">
ยกเลิกใช้งาน
</span>
<?php endif; ?>
</td>
<?php
// Strip common prefixes for sorting
$prefixes = ['นายแพทย์', 'แพทย์หญิง', 'ทันตแพทย์หญิง', 'ทันตแพทย์', 'เภสัชกรหญิง', 'เภสัชกร', 'นพ.', 'พญ.', 'ทพ.', 'ทพญ.', 'ภก.', 'ภญ.', 'นางสาว', 'น.ส.', 'นาง', 'นาย', 'ดร.', 'ผศ.', 'รศ.', 'ศ.', 'ว่าที่ ร.ต.', 'ร.ต.อ.', 'ร.ต.ท.', 'ร.ต.ต.'];
$sort_name = $user['name'];
foreach ($prefixes as $prefix) {
if (strpos($sort_name, $prefix) === 0) {
$sort_name = trim(substr($sort_name, strlen($prefix)));
break;
}
}
?>
<td class="px-6 py-4 text-slate-600" data-order="<?php echo htmlspecialchars($sort_name); ?>"><?php echo htmlspecialchars($user['name']); ?></td>
<td class="px-6 py-4">
<?php if($is_user_admin): ?>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700 border border-amber-200">
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span> ผู้ดูแลระบบ
</span>
<?php else: ?>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-blue-50 text-blue-600 border border-blue-100">
เจ้าหน้าที่ทั่วไป
</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 text-center">
<?php if($is_user_admin): ?>
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[11px] font-medium bg-slate-100 text-slate-500">
สิทธิ์ Admin
</span>
<?php elseif($can_search_name): ?>
<div class="flex items-center justify-center gap-2">
<span class="inline-flex items-center px-2 py-1 rounded-md text-[11px] font-medium bg-blue-50 text-blue-600 border border-blue-100">
ใช้งานได้
</span>
<button onclick="confirmRevokeSearchName('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-rose-50 text-rose-500 hover:bg-rose-500 hover:text-white transition-colors" title="ปิดสิทธิ์ค้นหาด้วยชื่อ">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
</button>
</div>
<?php else: ?>
<div class="flex items-center justify-center gap-2">
<span class="inline-flex items-center px-2 py-1 rounded-md text-[11px] font-medium bg-slate-50 text-slate-400 border border-slate-100">
ไม่มีสิทธิ์
</span>
<button onclick="confirmGrantSearchName('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-blue-50 text-blue-600 hover:bg-blue-500 hover:text-white transition-colors" title="เปิดสิทธิ์ค้นหาด้วยชื่อ">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"></path></svg>
</button>
</div>
<?php endif; ?>
</td>
<td class="px-6 py-4">
<?php if($has_2fa): ?>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700 border border-blue-200">
<span class="w-1.5 h-1.5 rounded-full bg-blue-500"></span> เปิดใช้งานแล้ว
</span>
<?php else: ?>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-600 border border-slate-200">
<span class="w-1.5 h-1.5 rounded-full bg-slate-400"></span> ยังไม่เปิดใช้งาน
</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 text-right">
<div class="flex items-center justify-end gap-2">
<?php if($is_user_admin): ?>
<button onclick="confirmRevokeAdmin('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-slate-100 text-slate-500 hover:bg-slate-500 hover:text-white transition-all shadow-sm" title="ปลดสิทธิ์ Admin">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7a4 4 0 11-8 0 4 4 0 018 0zM9 14a6 6 0 00-6 6v1h12v-1a6 6 0 00-6-6zM21 12h-6"></path></svg>
</button>
<?php else: ?>
<button onclick="confirmGrantAdmin('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-amber-50 text-amber-600 hover:bg-amber-500 hover:text-white transition-all shadow-sm ring-1 ring-amber-500/20" title="มอบสิทธิ์ Admin">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
</button>
<?php endif; ?>
<?php if($has_2fa): ?>
<button onclick="confirmReset2FA('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-rose-50 text-rose-500 hover:bg-rose-500 hover:text-white transition-all shadow-sm ring-1 ring-rose-500/20" title="รีเซ็ต 2FA">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
</button>
<?php else: ?>
<button onclick="generate2FA('<?php echo addslashes($user['loginname']); ?>', '<?php echo addslashes($user['name']); ?>')" class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-50 text-blue-600 hover:bg-blue-500 hover:text-white transition-all shadow-sm ring-1 ring-blue-500/20" title="สร้าง 2FA">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path></svg>
</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if(empty($users_list)): ?>
<div class="text-center py-12 text-slate-500">ไม่พบข้อมูลผู้ใช้งาน</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($tab === 'logs'): ?>
<!-- Tab: System Logs -->
<div class="glass-card overflow-hidden">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50">
<form method="GET" class="flex flex-wrap items-end gap-4">
<input type="hidden" name="tab" value="logs">
<div class="flex-1 min-w-[200px]">
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ค้นหา (Username/Details)</label>
<input type="text" name="search" value="<?php echo htmlspecialchars($filter_search); ?>" class="w-full form-input py-2 text-sm" placeholder="คำค้นหา...">
</div>
<div class="w-40">
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ประเภท (Action)</label>
<select name="action" class="w-full form-input py-2 text-sm">
<option value="">ทั้งหมด</option>
<?php foreach($actions_list as $act): ?>
<option value="<?php echo htmlspecialchars($act); ?>" <?php if($filter_action === $act) echo 'selected'; ?>><?php echo htmlspecialchars($act); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="w-36">
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ตั้งแต่วันที่</label>
<input type="date" name="start_date" value="<?php echo htmlspecialchars($filter_start); ?>" class="w-full form-input py-2 text-sm">
</div>
<div class="w-36">
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">ถึงวันที่</label>
<input type="date" name="end_date" value="<?php echo htmlspecialchars($filter_end); ?>" class="w-full form-input py-2 text-sm">
</div>
<div>
<div class="block text-xs font-semibold uppercase tracking-wider mb-1 opacity-0 pointer-events-none select-none hidden sm:block">Action</div>
<div class="flex gap-2">
<button type="submit" class="px-6 py-2 bg-slate-800 text-white font-medium rounded-lg hover:bg-slate-700 transition-colors shadow-sm text-sm border border-transparent flex items-center justify-center">
กรองข้อมูล
</button>
<a href="?tab=logs" class="px-4 py-2 text-slate-500 hover:text-slate-800 hover:bg-slate-100 font-medium text-sm transition-colors rounded-lg border border-transparent flex items-center justify-center">
ล้างค่า
</a>
</div>
</div>
</form>
</div>
<div class="overflow-x-auto p-4 sm:p-6">
<table id="logsTable" class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-50/50 border-b border-slate-200 text-slate-500 text-sm">
<th class="px-6 py-4 font-semibold text-center w-48">วันเวลา</th>
<th class="px-6 py-4 font-semibold text-center w-40">ผู้ใช้งาน</th>
<th class="px-6 py-4 font-semibold text-center w-40">เหตุการณ์</th>
<th class="px-6 py-4 font-semibold text-center">รายละเอียดเพิ่มเติม</th>
<th class="px-6 py-4 font-semibold text-center w-32">IP Address</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 text-sm">
<?php foreach($logs_list as $log): ?>
<tr class="hover:bg-slate-50/50 transition-colors">
<td class="px-6 py-4 text-slate-500 font-mono text-xs"><?php echo htmlspecialchars($log['created_at']); ?></td>
<td class="px-6 py-4 font-medium text-slate-800"><?php echo htmlspecialchars($log['username']); ?></td>
<td class="px-6 py-4">
<?php
$act = $log['action'];
$badge_class = "bg-slate-100 text-slate-700 border-slate-200";
if (strpos($act, 'SUCCESS') !== false) $badge_class = "bg-blue-100 text-blue-700 border-blue-200";
elseif (strpos($act, 'FAILED') !== false || strpos($act, 'ERROR') !== false) $badge_class = "bg-red-100 text-red-700 border-red-200";
elseif (strpos($act, 'VIEW') !== false) $badge_class = "bg-blue-100 text-blue-700 border-blue-200";
elseif (strpos($act, 'RESET') !== false || strpos($act, 'UPDATE') !== false) $badge_class = "bg-amber-100 text-amber-700 border-amber-200";
?>
<span class="inline-flex items-center px-2 py-0.5 rounded text-[11px] font-bold tracking-wide uppercase border <?php echo $badge_class; ?>">
<?php echo htmlspecialchars($act); ?>
</span>
</td>
<td class="px-6 py-4">
<?php
$det = $log['details'];
if (strpos($det, '{') === 0 || strpos($det, '[') === 0) {
// Try to prettify JSON
$decoded = json_decode($det, true);
if ($decoded !== null) {
echo '<div class="flex flex-wrap gap-2">';
foreach($decoded as $k => $v) {
echo '<span class="inline-flex bg-white border border-slate-200 rounded px-2 py-1 text-xs"><span class="text-slate-400 mr-1">'.$k.':</span><span class="font-medium text-slate-700">'.htmlspecialchars((string)$v).'</span></span>';
}
echo '</div>';
} else {
echo '<span class="inline-flex bg-slate-50 border border-slate-200 rounded-md px-2.5 py-1 text-xs font-medium text-slate-600">' . htmlspecialchars($det) . '</span>';
}
} else {
echo '<span class="inline-flex bg-slate-50 border border-slate-200 rounded-md px-2.5 py-1 text-xs font-medium text-slate-600">' . htmlspecialchars($det) . '</span>';
}
?>
</td>
<td class="px-6 py-4 text-slate-400 font-mono text-xs"><?php echo htmlspecialchars($log['ip_address']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if(empty($logs_list)): ?>
<div class="text-center py-12 text-slate-500">
<svg class="w-12 h-12 mx-auto text-slate-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
ไม่พบข้อมูลการใช้งานในช่วงเวลาดังกล่าว
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($tab === 'security'): ?>
<div class="bg-white/70 backdrop-blur-xl border border-white/50 rounded-[24px] shadow-glass overflow-hidden animate-enter" style="animation-delay: 0.1s;">
<div class="p-4 sm:p-6 border-b border-slate-100 bg-white/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h3 class="text-lg font-bold text-slate-800">รายการ IP ที่ถูกบล็อก</h3>
<p class="text-sm text-slate-500">IP Address ที่พยายามเข้าสู่ระบบผิดพลาดเกินกำหนด จะถูกบล็อก 15 นาที เพื่อป้องกันการโจมตี (Brute Force)</p>
</div>
<div>
<a href="?tab=security" class="inline-flex items-center gap-2 px-4 py-2 bg-white border border-slate-200 text-slate-600 text-sm font-medium rounded-lg hover:bg-slate-50 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
รีเฟรชข้อมูล
</a>
</div>
</div>
<div class="overflow-x-auto p-4 sm:p-6">
<table id="bannedTable" class="w-full text-left border-collapse">
<thead>
<tr class="bg-slate-50/50 border-b border-slate-200 text-slate-500 text-sm">
<th class="px-6 py-4 font-semibold text-center">IP Address</th>
<th class="px-6 py-4 font-semibold text-center">เวลาล่าสุด</th>
<th class="px-6 py-4 font-semibold text-center">จำนวนครั้งที่ผิด</th>
<th class="px-6 py-4 font-semibold text-center">ชื่อผู้ใช้ที่พยายามเข้าถึง</th>
<th class="px-6 py-4 font-semibold text-center">การจัดการ</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
<?php foreach($banned_ips as $bip): ?>
<tr class="hover:bg-slate-50/50 transition-colors">
<td class="px-6 py-4 font-medium text-rose-600 font-mono"><?php echo htmlspecialchars($bip['ip_address']); ?></td>
<td class="px-6 py-4 text-slate-600 text-sm"><?php echo htmlspecialchars($bip['last_attempt']); ?></td>
<td class="px-6 py-4 text-center">
<span class="inline-flex items-center justify-center min-w-[2rem] px-2 py-1 rounded-full bg-rose-100 text-rose-700 font-bold text-xs"><?php echo htmlspecialchars($bip['fail_count']); ?></span>
</td>
<td class="px-6 py-4 text-slate-500 text-sm">
<div class="max-w-[200px] truncate" title="<?php echo htmlspecialchars($bip['target_users']); ?>">
<?php echo htmlspecialchars($bip['target_users']); ?>
</div>
</td>
<td class="px-6 py-4 text-right">
<button onclick="confirmUnblockIP('<?php echo addslashes($bip['ip_address']); ?>')" class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg bg-blue-50 text-blue-600 hover:bg-blue-500 hover:text-white transition-all shadow-sm ring-1 ring-blue-500/20 text-xs font-medium">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"></path></svg>
ปลดบล็อก
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if(empty($banned_ips)): ?>
<div class="text-center py-12 text-slate-500">
<div class="flex flex-col items-center justify-center">
<svg class="w-12 h-12 mb-4 text-blue-400 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<span>ไม่มีรายการ IP ที่ถูกบล็อกในขณะนี้</span>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($show_2fa_modal): ?>
<!-- 2FA Generation Modal -->
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/50 backdrop-blur-sm animate-fade-in">
<div class="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden animate-enter">
<div class="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
<h3 class="text-lg font-bold text-slate-800">ตั้งค่า 2FA ใหม่</h3>
<a href="?tab=2fa" class="text-slate-400 hover:text-slate-600 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</a>
</div>
<div class="p-6">
<form method="POST" action="admin_settings.php?tab=2fa">
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
<input type="hidden" name="action" value="save_2fa">
<input type="hidden" name="target_user" value="<?php echo htmlspecialchars($modal_data['username']); ?>">
<input type="hidden" name="secretkey" value="<?php echo htmlspecialchars($modal_data['secret']); ?>">
<div class="text-center mb-6">
<p class="text-sm text-slate-500 mb-2">ให้ผู้ใช้งานสแกน QR Code นี้ด้วยแอปพลิเคชัน Authenticator (เช่น Google Authenticator)</p>
<div class="inline-block p-2 bg-white border-2 border-slate-100 rounded-xl mb-4">
<img src="<?php echo htmlspecialchars($modal_data['qr']); ?>" alt="2FA QR Code" class="w-48 h-48">
</div>
<div class="bg-slate-50 p-3 rounded-lg border border-slate-100">
<div class="text-xs text-slate-400 mb-1">บัญชีผู้ใช้</div>
<div class="font-medium text-slate-800"><?php echo htmlspecialchars($modal_data['name']); ?> (<?php echo htmlspecialchars($modal_data['username']); ?>)</div>
</div>
</div>
<div class="flex gap-3 mt-6">
<button type="submit" class="flex-1 btn-primary">บันทึก 2FA</button>
<a href="?tab=2fa" class="flex-1 px-4 py-2 text-center text-slate-600 bg-slate-100 hover:bg-slate-200 rounded-full font-medium transition-colors">ยกเลิก</a>
</div>
</form>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php require_once("../components/layout_footer.php"); ?>
</main>
<script src="../assets/js/micro-interactions.js"></script>
<script>
function formatSqlInput(inputId) {
if (typeof sqlFormatter === 'undefined') {
alert("SQL Formatter library is not loaded yet. Please wait.");
return;
}
const textarea = document.getElementById(inputId);
if (!textarea || !textarea.value.trim()) return;
try {
textarea.value = sqlFormatter.format(textarea.value, {
language: 'mysql',
uppercase: true
});
} catch (e) {
alert("Cannot format SQL: Syntax Error.");
console.error(e);
}
}
</script>
<script>
$(document).ready(function() {
// Initialize DataTable only if it exists
if ($('#usersTable').length > 0) {
var table = $('#usersTable').DataTable({
"dom": '<"flex flex-wrap items-center justify-end gap-4 mb-4"f>rt<"flex flex-wrap items-center justify-between gap-4 mt-4"ip>',
"pageLength": 10,
"lengthChange": false,
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
},
"columnDefs": [
{ "className": "dt-center", "targets": "_all" }
],
"order": [[ 1, "asc" ]]
});
// Initialize Buttons
new $.fn.dataTable.Buttons(table, {
buttons: [
{
extend: 'excelHtml5',
text: '<svg class="w-4 h-4 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg> ส่งออก Excel',
title: 'ข้อมูลผู้ใช้งานจากระบบ HOSxP Web Services',
className: 'btn-export-excel',
exportOptions: {
columns: [0, 1, 2, 4] // Export Username, Name, Role, 2FA
}
}
]
});
// Move buttons to custom container
table.buttons().container().appendTo('#exportBtnContainer');
// Add custom filter for Admin roles and Status
$.fn.dataTable.ext.search.push(
function( settings, data, dataIndex ) {
if (settings.nTable.id !== 'usersTable') return true;
var roleFilter = $('#roleFilter').val();
var statusFilter = $('#statusFilter').val();
var twoFaFilter = $('#twoFaFilter').val();
// Check Status
var tr = settings.aoData[dataIndex].nTr;
var rowStatus = tr ? tr.getAttribute('data-status') : 'active';
var statusMatch = true;
if (statusFilter === 'active') {
statusMatch = (rowStatus === 'active');
} else if (statusFilter === 'disabled') {
statusMatch = (rowStatus === 'disabled');
}
if (!statusMatch) return false;
// Check 2FA Status
if (twoFaFilter) {
var twoFaColumn = data[4] || ""; // Column 4 is 2FA Status
if (twoFaFilter === 'enabled' && !twoFaColumn.includes('เปิดใช้งานแล้ว')) return false;
if (twoFaFilter === 'disabled' && !twoFaColumn.includes('ยังไม่เปิดใช้งาน')) return false;
}
// Check Role
if (!roleFilter) return true; // Show all
var roleColumn = data[2] || ""; // Column index 2 is Role
var searchNameColumn = data[3] || ""; // Column index 3 is Search Name Permission
if (roleFilter === 'admin') {
return roleColumn.includes('ผู้ดูแลระบบ');
} else if (roleFilter === 'user') {
return roleColumn.includes('เจ้าหน้าที่ทั่วไป');
} else if (roleFilter === 'search_name') {
return searchNameColumn.includes('ใช้งานได้');
}
return true;
}
);
$('#roleFilter, #statusFilter, #twoFaFilter').on('change', function() {
table.draw();
});
}
if ($('#logsTable').length > 0) {
$('#logsTable').DataTable({
"pageLength": 25,
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
},
"columnDefs": [
{ "className": "dt-center", "targets": "_all" }
],
"order": [[ 0, "desc" ]]
});
}
if ($('#bannedTable').length > 0) {
$('#bannedTable').DataTable({
"pageLength": 10,
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
},
"columnDefs": [
{ "className": "dt-center", "targets": "_all" }
],
"order": [[ 1, "desc" ]]
});
}
});
</script>
</body>
</html>