906 lines
54 KiB
PHP
906 lines
54 KiB
PHP
<?php
|
|
// views/tab_settings.php
|
|
|
|
$isSettingsAjax = isset($_GET['ajax_settings']) && $_GET['ajax_settings'] == '1';
|
|
$isTestSqlAjax = isset($_GET['ajax_test_sql']) && $_GET['ajax_test_sql'] == '1';
|
|
$isSaveSingleSqlAjax = isset($_GET['ajax_save_single_sql']) && $_GET['ajax_save_single_sql'] == '1';
|
|
$isAuthSettingsAjax = isset($_GET['ajax_auth_settings']) && $_GET['ajax_auth_settings'] == '1';
|
|
|
|
if ($isAuthSettingsAjax) {
|
|
while (ob_get_level()) { ob_end_clean(); }
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
require_once __DIR__ . '/../includes/db.php';
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$raw_post = file_get_contents('php://input');
|
|
$data = json_decode($raw_post, true);
|
|
if ($data && isset($data['password'])) {
|
|
if (isset($settings_password) && $data['password'] === $settings_password) {
|
|
echo json_encode(['status' => 'success']);
|
|
} else {
|
|
echo json_encode(['status' => 'error', 'message' => 'รหัสผ่านไม่ถูกต้อง']);
|
|
}
|
|
}
|
|
}
|
|
exit;
|
|
}
|
|
|
|
if ($isTestSqlAjax) {
|
|
while (ob_get_level()) { ob_end_clean(); }
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
require_once __DIR__ . '/../includes/db.php';
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$raw_post = file_get_contents('php://input');
|
|
$data = json_decode($raw_post, true);
|
|
|
|
$sql = '';
|
|
if ($data && isset($data['sql_text_b64'])) {
|
|
$sql = trim(urldecode(base64_decode($data['sql_text_b64'])));
|
|
} elseif ($data && isset($data['sql_text'])) {
|
|
$sql = trim($data['sql_text']);
|
|
}
|
|
|
|
if ($sql !== '') {
|
|
$current_month = (int)date('m');
|
|
$current_year = (int)date('Y');
|
|
$current_fy = ($current_month >= 10) ? $current_year + 1 : $current_year;
|
|
$params = [];
|
|
if (strpos($sql, ':start_ly') !== false) $params['start_ly'] = ($current_fy - 2) . '-10-01';
|
|
if (strpos($sql, ':end_ly') !== false) $params['end_ly'] = ($current_fy - 1) . '-09-30';
|
|
if (preg_match('/:start\b/', $sql)) $params['start'] = date('Y-m-01');
|
|
if (preg_match('/:end\b/', $sql)) $params['end'] = date('Y-m-d');
|
|
|
|
try {
|
|
if (stripos($sql, 'SELECT') === 0 && stripos($sql, 'LIMIT') === false) {
|
|
$sql_to_run = $sql . " LIMIT 5";
|
|
} else {
|
|
$sql_to_run = $sql;
|
|
}
|
|
|
|
$sql_compiled = $sql;
|
|
foreach ($params as $k => $v) {
|
|
$sql_compiled = preg_replace('/:' . $k . '\b/', "'" . $v . "'", $sql_compiled);
|
|
}
|
|
|
|
$stmt = $pdo->prepare($sql_to_run);
|
|
$stmt->execute($params);
|
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
echo json_encode(['status' => 'success', 'data' => $results, 'params_used' => $params, 'sql_compiled' => $sql_compiled]);
|
|
} catch (\Exception $e) {
|
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
|
}
|
|
} else {
|
|
echo json_encode(['status' => 'error', 'message' => 'Invalid JSON']);
|
|
}
|
|
}
|
|
exit;
|
|
}
|
|
|
|
if ($isSaveSingleSqlAjax) {
|
|
while (ob_get_level()) { ob_end_clean(); }
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
require_once __DIR__ . '/../includes/db.php';
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$raw_post = file_get_contents('php://input');
|
|
$data = json_decode($raw_post, true);
|
|
|
|
$sql_text = '';
|
|
if ($data && isset($data['sql_text_b64'])) {
|
|
$sql_text = trim(urldecode(base64_decode($data['sql_text_b64'])));
|
|
} elseif ($data && isset($data['sql_text'])) {
|
|
$sql_text = trim($data['sql_text']);
|
|
}
|
|
|
|
if ($data && isset($data['section'], $data['key']) && $sql_text !== '') {
|
|
$success = false;
|
|
$msg = '';
|
|
if (isset($pdo_dashboard) && $pdo_dashboard) {
|
|
try {
|
|
$stmt = $pdo_dashboard->prepare("
|
|
INSERT INTO waiting (section_name, query_key, sql_text)
|
|
VALUES (:section_name, :query_key, :sql_text)
|
|
ON DUPLICATE KEY UPDATE sql_text = :sql_text2
|
|
");
|
|
$stmt->execute([
|
|
':section_name' => $data['section'],
|
|
':query_key' => $data['key'],
|
|
':sql_text' => $sql_text,
|
|
':sql_text2' => $sql_text
|
|
]);
|
|
$success = true;
|
|
$msg = 'บันทึกคำสั่งสำเร็จ';
|
|
} catch (\Exception $e) {
|
|
$msg = 'DB Error: ' . $e->getMessage();
|
|
}
|
|
} else {
|
|
$sql_config_path = __DIR__ . '/../includes/sql_config.json';
|
|
$current_config = file_exists($sql_config_path) ? json_decode(file_get_contents($sql_config_path), true) : [];
|
|
if (!is_array($current_config)) $current_config = [];
|
|
if (!isset($current_config[$data['section']])) $current_config[$data['section']] = [];
|
|
$current_config[$data['section']][$data['key']] = $sql_text;
|
|
if (file_put_contents($sql_config_path, json_encode($current_config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) !== false) {
|
|
$success = true;
|
|
$msg = 'บันทึกคำสั่งสำเร็จ (JSON)';
|
|
} else {
|
|
$msg = 'ไม่สามารถเขียนไฟล์ JSON ได้';
|
|
}
|
|
}
|
|
echo json_encode(['status' => $success ? 'success' : 'error', 'message' => $msg]);
|
|
} else {
|
|
echo json_encode(['status' => 'error', 'message' => 'Invalid Request']);
|
|
}
|
|
}
|
|
exit;
|
|
}
|
|
|
|
if ($isSettingsAjax) {
|
|
while (ob_get_level()) { ob_end_clean(); }
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
require_once __DIR__ . '/../includes/db.php';
|
|
require_once __DIR__ . '/../includes/sql_config.php'; // เพื่อให้ได้ตัวแปร $sql_config ล่าสุด
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$raw_post = file_get_contents('php://input');
|
|
$new_config = json_decode($raw_post, true);
|
|
|
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
|
|
$success = false;
|
|
$msg = '';
|
|
|
|
// 1. ถ้ามี Database Dashboard ให้บันทึกลง Database
|
|
if (isset($pdo_dashboard) && $pdo_dashboard) {
|
|
try {
|
|
$pdo_dashboard->beginTransaction();
|
|
|
|
// ใช้ UPSERT เพื่อ Insert ถ้ายังไม่มี หรือ Update ถ้ามีแล้ว
|
|
$stmt = $pdo_dashboard->prepare("
|
|
INSERT INTO waiting (section_name, query_key, sql_text)
|
|
VALUES (:section_name, :query_key, :sql_text)
|
|
ON DUPLICATE KEY UPDATE sql_text = :sql_text2
|
|
");
|
|
|
|
foreach ($new_config as $section => $keys) {
|
|
foreach ($keys as $key => $sql_text) {
|
|
$stmt->execute([
|
|
':section_name' => $section,
|
|
':query_key' => $key,
|
|
':sql_text' => $sql_text,
|
|
':sql_text2' => $sql_text
|
|
]);
|
|
}
|
|
}
|
|
|
|
$pdo_dashboard->commit();
|
|
$success = true;
|
|
$msg = 'บันทึกการตั้งค่าลงฐานข้อมูลเรียบร้อยแล้ว';
|
|
} catch (\PDOException $e) {
|
|
$pdo_dashboard->rollBack();
|
|
$success = false;
|
|
$msg = 'เกิดข้อผิดพลาดฐานข้อมูล: ' . $e->getMessage();
|
|
}
|
|
} else {
|
|
// 2. Fallback ไปบันทึกลง JSON
|
|
$sql_config_path = __DIR__ . '/../includes/sql_config.json';
|
|
$current_config = file_exists($sql_config_path) ? json_decode(file_get_contents($sql_config_path), true) : [];
|
|
if (!is_array($current_config)) $current_config = [];
|
|
$merged_config = array_replace_recursive($current_config, $new_config);
|
|
|
|
$saved = file_put_contents($sql_config_path, json_encode($merged_config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
|
if ($saved !== false) {
|
|
$success = true;
|
|
$msg = 'บันทึกการตั้งค่าลงไฟล์ JSON แล้ว (ไม่ได้เชื่อมต่อฐานข้อมูล)';
|
|
} else {
|
|
$success = false;
|
|
$msg = 'ไม่สามารถบันทึกไฟล์ JSON ได้ (กรุณาตรวจสอบสิทธิ์โฟลเดอร์)';
|
|
}
|
|
}
|
|
|
|
if ($success) {
|
|
echo json_encode(['status' => 'success', 'message' => $msg]);
|
|
} else {
|
|
echo json_encode(['status' => 'error', 'message' => $msg]);
|
|
}
|
|
|
|
} else {
|
|
echo json_encode(['status' => 'error', 'message' => 'ข้อมูลที่ส่งมาไม่ถูกต้อง (Invalid JSON)']);
|
|
}
|
|
} else {
|
|
// GET request: ส่งค่า $sql_config ซึ่งอาจจะดึงจาก DB หรือ JSON มาแล้ว
|
|
$json_out = @json_encode(['status' => 'success', 'data' => $sql_config], JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
|
if (!$json_out) {
|
|
echo json_encode(['status' => 'error', 'message' => 'JSON Encode Error: ' . json_last_error_msg()]);
|
|
} else {
|
|
echo $json_out;
|
|
}
|
|
}
|
|
exit;
|
|
}
|
|
?>
|
|
|
|
<div id="tab-settings" class="tab-content hidden animate-fade-in">
|
|
<div class="card-premium p-6">
|
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4 border-b border-slate-100 pb-4">
|
|
<h3 class="text-xl font-bold text-slate-800 flex items-center">
|
|
<i class="fa-solid fa-gear text-slate-400 mr-3 text-2xl"></i>
|
|
ตั้งค่าชุดคำสั่ง SQL (SQL Settings)
|
|
</h3>
|
|
</div>
|
|
|
|
<div class="text-sm text-slate-600 mb-6 border-l-4 border-amber-500 pl-4 py-3 bg-amber-50 rounded-r shadow-sm">
|
|
<b class="text-amber-800"><i class="fa-solid fa-triangle-exclamation mr-1"></i> ข้อควรระวัง:</b> การแก้ไขคำสั่ง SQL ควรทำโดยผู้ที่มีความรู้ด้านโครงสร้างฐานข้อมูล<br>
|
|
โปรด <span class="text-red-500 font-bold">อย่าลบตัวแปร Binding</span> ออกจากคำสั่งเดิม ไม่เช่นนั้นระบบอาจเกิดข้อผิดพลาดในการรันคำสั่งได้<br><br>
|
|
<b class="text-slate-700"><i class="fa-solid fa-circle-info text-blue-500 mr-1"></i> คำแนะนำเกี่ยวกับตัวแปรวันที่ (Binding Parameters):</b>
|
|
<ul class="list-disc ml-6 mt-2 space-y-1">
|
|
<li><code class="bg-white border border-slate-200 px-1.5 py-0.5 rounded text-pink-600 font-mono text-xs">:start</code> - วันที่เริ่มต้นที่ถูกเลือกจากหน้า Dashboard (เช่น '2023-10-01')</li>
|
|
<li><code class="bg-white border border-slate-200 px-1.5 py-0.5 rounded text-pink-600 font-mono text-xs">:end</code> - วันที่สิ้นสุดที่ถูกเลือกจากหน้า Dashboard (เช่น '2024-09-30')</li>
|
|
<li><code class="bg-white border border-slate-200 px-1.5 py-0.5 rounded text-pink-600 font-mono text-xs">:start_ly</code> - วันที่เริ่มต้นของปีก่อนหน้า (ใช้สำหรับทำเส้นกราฟแนวโน้มเปรียบเทียบปีที่แล้ว)</li>
|
|
<li><code class="bg-white border border-slate-200 px-1.5 py-0.5 rounded text-pink-600 font-mono text-xs">:end_ly</code> - วันที่สิ้นสุดของปีก่อนหน้า (ใช้สำหรับทำเส้นกราฟแนวโน้มเปรียบเทียบปีที่แล้ว)</li>
|
|
</ul>
|
|
<br>
|
|
<b class="text-slate-700"><i class="fa-solid fa-play text-blue-500 mr-1"></i> คำแนะนำการใช้งานปุ่ม "ทดสอบ":</b>
|
|
<ul class="list-disc ml-6 mt-2 space-y-1">
|
|
<li>คุณสามารถปล่อยตัวแปร <code class="bg-white border border-slate-200 px-1.5 py-0.5 rounded text-pink-600 font-mono text-xs">:start</code> และ <code class="bg-white border border-slate-200 px-1.5 py-0.5 rounded text-pink-600 font-mono text-xs">:end</code> ไว้ในคำสั่งตามเดิมได้เลย ไม่ต้องลบออก</li>
|
|
<li>เมื่อกดปุ่มทดสอบ ระบบจะ <b class="text-blue-600">จำลองวันที่ปัจจุบัน</b> เข้าไปแทนที่ตัวแปรเหล่านั้นให้อัตโนมัติ (เช่น วันที่ 1 ถึง วันนี้ของเดือนปัจจุบัน)</li>
|
|
<li>ผลลัพธ์จะถูกดึงมาแสดงผลเป็นตารางตัวอย่างเพียง <b class="text-emerald-600">5 แถวแรก</b> เท่านั้น เพื่อให้ตรวจสอบความถูกต้องได้รวดเร็ว</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div id="settings-loading" class="text-center py-12">
|
|
<i class="fa-solid fa-circle-notch fa-spin text-4xl text-blue-500 mb-4"></i>
|
|
<p class="text-slate-500 font-medium tracking-wide">กำลังโหลดข้อมูลตั้งค่า...</p>
|
|
</div>
|
|
|
|
<form id="form-sql-settings" class="hidden space-y-6">
|
|
<!-- Sections will be injected here via JS -->
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const settingsSchema = {
|
|
'overview': {
|
|
title: 'ภาพรวมโรงพยาบาล (Overview)',
|
|
icon: 'fa-chart-pie',
|
|
color: 'text-blue-500',
|
|
fields: {
|
|
'opd_count': 'ยอดรวมผู้ป่วยนอก (OPD)',
|
|
'ipd_count': 'ยอดรวมผู้ป่วยใน (IPD)',
|
|
'cmi_avg': 'ค่าเฉลี่ย CMI',
|
|
'trend_opd': 'แนวโน้มผู้ป่วยนอก (Trend)',
|
|
'trend_ipd': 'แนวโน้มผู้ป่วยใน (Trend)',
|
|
'refer_in': 'รับส่งต่อ (Refer In)',
|
|
'refer_out': 'ส่งต่อ (Refer Out)',
|
|
'doc_before_9': 'แพทย์ลงตรวจก่อน 9.00 น.',
|
|
'satisfaction': 'ความพึงพอใจ',
|
|
'refer_back': 'ส่งกลับ (Refer Back)'
|
|
},
|
|
hints: {
|
|
'opd_count': 'ระบบต้องการเพียง 1 คอลัมน์ (ผลลัพธ์ยอดรวม OPD)',
|
|
'ipd_count': 'ระบบต้องการเพียง 1 คอลัมน์ (ผลลัพธ์ยอดรวม IPD)',
|
|
'cmi_avg': 'ระบบต้องการเพียง 1 คอลัมน์ (ผลลัพธ์ค่าเฉลี่ย CMI)',
|
|
'trend_opd': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (เดือนปี YYYY-MM), <b>avg_wait</b> (เวลารอคอยเฉลี่ย)',
|
|
'trend_ipd': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (เดือนปี YYYY-MM), <b>occ_rate</b> (อัตราครองเตียง)',
|
|
'refer_in': 'ระบบต้องการเพียง 1 คอลัมน์ (ยอดรวม Refer In)',
|
|
'refer_out': 'ระบบต้องการเพียง 1 คอลัมน์ (ยอดรวม Refer Out)',
|
|
'doc_before_9': 'ระบบต้องการเพียง 1 คอลัมน์ (เปอร์เซ็นต์แพทย์ออกตรวจก่อน 9 โมง)',
|
|
'satisfaction': 'ระบบต้องการเพียง 1 คอลัมน์ (คะแนนความพึงพอใจ)',
|
|
'refer_back': 'ระบบต้องการเพียง 1 คอลัมน์ (ยอดส่งกลับ)'
|
|
}
|
|
},
|
|
'opd': {
|
|
title: 'ลดรอคอย OPD',
|
|
icon: 'fa-stopwatch',
|
|
color: 'text-blue-400',
|
|
fields: {
|
|
'flowchart': 'ขั้นตอนการบริการ (Flowchart)',
|
|
'trend': 'แนวโน้มการรอคอย (Trend)',
|
|
'clinics': 'เวลารอคอยแยกตามคลินิก',
|
|
'tele_total': 'ยอดรวม Telemedicine',
|
|
'tele_clinics': 'Telemedicine แยกคลินิก',
|
|
'refill_total': 'ยอดรวมส่งยาที่บ้าน (Refill)',
|
|
'refill_trend': 'แนวโน้มส่งยาที่บ้าน (Trend)',
|
|
'refer_in': 'รับ Refer In',
|
|
'refer_out': 'ส่ง Refer Out',
|
|
'refer_back': 'ส่งกลับ (Refer Back)',
|
|
'refer_in_dest': 'ปลายทาง Refer In',
|
|
'refer_out_dest': 'ปลายทาง Refer Out',
|
|
'refer_in_top': '10 อันดับโรค Refer In',
|
|
'refer_out_top': '10 อันดับโรค Refer Out',
|
|
'doc_time': 'เวลาแพทย์ลงตรวจ'
|
|
},
|
|
hints: {
|
|
'flowchart': 'ระบบต้องการ 6 คอลัมน์: <b>step1, step2, step3, step4, step5, custom_total</b>',
|
|
'trend': 'ระบบต้องการ 3 คอลัมน์: <b>ym</b> (YYYY-MM), <b>avg_wait</b>, <b>pt_count</b>',
|
|
'clinics': 'ระบบต้องการ 2 คอลัมน์: <b>main_dep</b> (รหัสแผนก), <b>start2finish</b> (เวลารวม)',
|
|
'tele_total': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'tele_clinics': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อแผนก), <b>val</b> (ยอดรวม)',
|
|
'refill_total': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'refill_trend': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cnt</b> (ยอดรวม)',
|
|
'refer_in': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'refer_out': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'refer_back': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'refer_in_dest': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ปลายทาง), <b>val</b> (ยอดรวม)',
|
|
'refer_out_dest': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ปลายทาง), <b>val</b> (ยอดรวม)',
|
|
'refer_in_top': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>a</b> (ยอดรวม)',
|
|
'refer_out_top': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>a</b> (ยอดรวม)',
|
|
'doc_time': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อแผนก), <b>avg_time</b> (เวลาเฉลี่ย)'
|
|
}
|
|
},
|
|
'ipd': {
|
|
title: 'ลดแออัด IPD',
|
|
icon: 'fa-bed-pulse',
|
|
color: 'text-emerald-500',
|
|
fields: {
|
|
'occ_all': 'อัตราครองเตียง (All)',
|
|
'occ_max': 'อัตราครองเตียง (Max Ward)',
|
|
'occ_min': 'อัตราครองเตียง (Min Ward)',
|
|
'occ_wards': 'อัตราครองเตียง (แยก Wards)',
|
|
'refer_in': 'ยอดรวม Refer In',
|
|
'refer_out': 'ยอดรวม Refer Out',
|
|
'refer_back': 'ยอดรวม Refer Back',
|
|
'refer_in_trend': 'แนวโน้ม Refer In (Trend)',
|
|
'refer_out_trend': 'แนวโน้ม Refer Out (Trend)',
|
|
'refer_in_top': '10 อันดับโรค Refer In',
|
|
'refer_out_top': '10 อันดับโรค Refer Out',
|
|
'refer_back_dest_trend': 'จำนวนที่ส่งกลับ แยกปลายทาง (รายเดือน)',
|
|
'refer_back_top': '10 อันดับโรคที่ส่งกลับ',
|
|
'refer_back_cause': 'สาเหตุการส่งกลับ (Refer Back)',
|
|
'adjrw_in_low': 'AdjRW Refer In (Low)',
|
|
'adjrw_in_high': 'AdjRW Refer In (High)',
|
|
'adjrw_out_low': 'AdjRW Refer Out (Low)',
|
|
'adjrw_out_high': 'AdjRW Refer Out (High)'
|
|
},
|
|
hints: {
|
|
'occ_all': 'ระบบต้องการ 1 คอลัมน์ (เปอร์เซ็นต์อัตราครองเตียง)',
|
|
'occ_max': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อตึก), <b>occ</b> (เปอร์เซ็นต์)',
|
|
'occ_min': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อตึก), <b>occ</b> (เปอร์เซ็นต์)',
|
|
'occ_wards': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อตึก), <b>occ</b> (เปอร์เซ็นต์)',
|
|
'refer_in': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'refer_out': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'refer_back': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'refer_in_trend': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cnt</b> (จำนวนเคส)',
|
|
'refer_out_trend': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cnt</b> (จำนวนเคส)',
|
|
'refer_in_top': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>a</b> (จำนวนเคส)',
|
|
'refer_out_top': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>a</b> (จำนวนเคส)',
|
|
'refer_back_dest_trend': 'ระบบต้องการคอลัมน์ <b>dest</b> (ปลายทาง), <b>cnt</b> (จำนวนเคส)',
|
|
'refer_back_top': 'ระบบต้องการคอลัมน์ <b>name</b> (ชื่อโรค), <b>a</b> (จำนวนเคส)',
|
|
'refer_back_cause': 'ระบบต้องการคอลัมน์ <b>cause</b> (สาเหตุ), <b>cnt</b> (จำนวนเคส)',
|
|
'adjrw_in_low': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cnt</b> (จำนวนเคส)',
|
|
'adjrw_in_high': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cnt</b> (จำนวนเคส)',
|
|
'adjrw_out_low': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cnt</b> (จำนวนเคส)',
|
|
'adjrw_out_high': 'ระบบต้องการ 2 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cnt</b> (จำนวนเคส)'
|
|
}
|
|
},
|
|
'stats': {
|
|
title: 'ข้อมูลสถิติ (Statistics)',
|
|
icon: 'fa-chart-bar',
|
|
color: 'text-indigo-500',
|
|
fields: {
|
|
'disease_opd': '10 อันดับโรค OPD',
|
|
'disease_ipd': '10 อันดับโรค IPD',
|
|
'disease_ipd_death': '10 อันดับโรค IPD เสียชีวิต',
|
|
'disease_er_death': '10 อันดับโรค ER เสียชีวิต',
|
|
'refer_overview': 'ภาพรวมโรค Refer',
|
|
'refer_ambulance': 'ส่งต่อด้วยรถพยาบาล',
|
|
'refer_ship': 'ส่งต่อทางเรือ',
|
|
'refer_air': 'ส่งต่อทางอากาศ',
|
|
'total_opd': 'ยอดรับบริการรวม OPD',
|
|
'total_ipd': 'ยอดรับบริการรวม IPD',
|
|
'total_er': 'ยอดรับบริการรวม ER',
|
|
'total_ipd_death': 'ยอดเสียชีวิต IPD',
|
|
'total_er_death': 'ยอดเสียชีวิต ER',
|
|
'total_doa': 'ยอด DOA',
|
|
'service_rights': 'สัดส่วนสิทธิการรักษา',
|
|
'thai_opd': 'สัญชาติไทย OPD',
|
|
'thai_ipd': 'สัญชาติไทย IPD',
|
|
'foreign_opd': 'ต่างชาติ OPD',
|
|
'foreign_ipd': 'ต่างชาติ IPD',
|
|
'alien_opd': 'ต่างด้าว OPD',
|
|
'alien_ipd': 'ต่างด้าว IPD',
|
|
'cmi_avg': 'ค่าเฉลี่ย CMI',
|
|
'adjrw_sum': 'ผลรวม AdjRW',
|
|
'trend_cmi': 'กราฟรายเดือน CMI / AdjRW'
|
|
},
|
|
hints: {
|
|
'disease_opd': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>val</b> (จำนวนเคส)',
|
|
'disease_ipd': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>val</b> (จำนวนเคส)',
|
|
'disease_ipd_death': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>val</b> (จำนวนเคส)',
|
|
'disease_er_death': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อโรค), <b>val</b> (จำนวนเคส)',
|
|
'refer_overview': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (ชื่อประเภท/หมวด), <b>val</b> (จำนวนเคส)',
|
|
'refer_ambulance': 'ระบบต้องการ 1 คอลัมน์ (จำนวนเคส)',
|
|
'refer_ship': 'ระบบต้องการ 1 คอลัมน์ (จำนวนเคส)',
|
|
'refer_air': 'ระบบต้องการ 1 คอลัมน์ (จำนวนเคส)',
|
|
'total_opd': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'total_ipd': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'total_er': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'total_ipd_death': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'total_er_death': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'total_doa': 'ระบบต้องการ 1 คอลัมน์ (ยอดรวม)',
|
|
'service_rights': 'ระบบต้องการ 2 คอลัมน์: <b>name</b> (สิทธิการรักษา), <b>val</b> (ยอดรวม)',
|
|
'thai_opd': 'ระบบต้องการ 1 คอลัมน์',
|
|
'thai_ipd': 'ระบบต้องการ 1 คอลัมน์',
|
|
'foreign_opd': 'ระบบต้องการ 1 คอลัมน์',
|
|
'foreign_ipd': 'ระบบต้องการ 1 คอลัมน์',
|
|
'alien_opd': 'ระบบต้องการ 1 คอลัมน์',
|
|
'alien_ipd': 'ระบบต้องการ 1 คอลัมน์',
|
|
'cmi_avg': 'ระบบต้องการ 1 คอลัมน์ (ค่าเฉลี่ย)',
|
|
'adjrw_sum': 'ระบบต้องการ 1 คอลัมน์ (ผลรวม)',
|
|
'trend_cmi': 'ระบบต้องการ 3 คอลัมน์: <b>ym</b> (YYYY-MM), <b>cmi</b> (ค่าเฉลี่ย), <b>adjrw</b> (ผลรวม)'
|
|
}
|
|
}
|
|
};
|
|
|
|
let currentConfigData = {};
|
|
|
|
function loadSqlConfig() {
|
|
document.getElementById('settings-loading').style.display = 'block';
|
|
document.getElementById('form-sql-settings').style.display = 'none';
|
|
|
|
fetch('?ajax_settings=1')
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
if(res.status === 'success') {
|
|
currentConfigData = res.data || {};
|
|
renderSettingsForm(currentConfigData);
|
|
} else {
|
|
Swal.fire({
|
|
title: 'เกิดข้อผิดพลาด',
|
|
text: res.message || 'ไม่สามารถโหลดข้อมูลได้',
|
|
icon: 'error',
|
|
confirmButtonText: 'ตกลง',
|
|
confirmButtonColor: '#3b82f6'
|
|
});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
Swal.fire({
|
|
title: 'เกิดข้อผิดพลาด',
|
|
text: 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้',
|
|
icon: 'error',
|
|
confirmButtonText: 'ตกลง',
|
|
confirmButtonColor: '#3b82f6'
|
|
});
|
|
})
|
|
.finally(() => {
|
|
document.getElementById('settings-loading').style.display = 'none';
|
|
document.getElementById('form-sql-settings').style.display = 'block';
|
|
});
|
|
}
|
|
|
|
function renderSettingsForm(data) {
|
|
const form = document.getElementById('form-sql-settings');
|
|
form.innerHTML = '';
|
|
|
|
for (const [sectionKey, sectionInfo] of Object.entries(settingsSchema)) {
|
|
const sectionDiv = document.createElement('div');
|
|
sectionDiv.className = 'border border-slate-200 rounded-xl overflow-hidden bg-white shadow-sm';
|
|
|
|
const header = document.createElement('div');
|
|
header.className = 'bg-slate-50 hover:bg-slate-100 transition-colors px-5 py-4 border-b border-slate-200 flex justify-between items-center cursor-pointer select-none';
|
|
header.onclick = function() {
|
|
const body = sectionDiv.querySelector('.section-body');
|
|
const isCurrentlyHidden = body.classList.contains('hidden');
|
|
|
|
// ยุบทุกหมวดก่อน (Collapse all)
|
|
document.querySelectorAll('#form-sql-settings .section-body').forEach(el => {
|
|
el.classList.add('hidden');
|
|
});
|
|
document.querySelectorAll('#form-sql-settings .toggle-icon').forEach(icon => {
|
|
icon.classList.remove('fa-chevron-up');
|
|
icon.classList.add('fa-chevron-down');
|
|
});
|
|
|
|
// เปิดเฉพาะหมวดนี้ถ้าก่อนหน้านี้มันถูกปิดอยู่
|
|
if (isCurrentlyHidden) {
|
|
const icon = header.querySelector('.toggle-icon');
|
|
body.classList.remove('hidden');
|
|
icon.classList.remove('fa-chevron-down');
|
|
icon.classList.add('fa-chevron-up');
|
|
}
|
|
};
|
|
|
|
header.innerHTML = `
|
|
<h4 class="font-bold text-slate-700 text-lg flex items-center">
|
|
<i class="fa-solid ${sectionInfo.icon} ${sectionInfo.color} mr-3"></i>
|
|
${sectionInfo.title}
|
|
</h4>
|
|
<i class="fa-solid fa-chevron-down text-slate-400 toggle-icon transition-transform"></i>
|
|
`;
|
|
|
|
sectionDiv.appendChild(header);
|
|
|
|
const bodyDiv = document.createElement('div');
|
|
bodyDiv.className = 'section-body hidden p-5 space-y-6';
|
|
|
|
let index = 1;
|
|
for (const [fieldKey, fieldLabel] of Object.entries(sectionInfo.fields)) {
|
|
const val = (data[sectionKey] && data[sectionKey][fieldKey]) ? data[sectionKey][fieldKey] : '';
|
|
const hint = (sectionInfo.hints && sectionInfo.hints[fieldKey]) ? sectionInfo.hints[fieldKey] : null;
|
|
const hintHtml = hint ? `<div class="mt-2 text-[11.5px] text-amber-800 bg-amber-50/80 border border-amber-200/60 rounded p-2.5 mb-1"><i class="fa-solid fa-lightbulb text-amber-500 mr-1.5"></i> <strong>ตัวแปรที่จำเป็น (Alias):</strong> ${hint}</div>` : '';
|
|
|
|
const fieldDiv = document.createElement('div');
|
|
fieldDiv.className = 'flex flex-col border border-slate-100 p-4 rounded-lg bg-white shadow-sm';
|
|
fieldDiv.innerHTML = `
|
|
<label class="block text-sm font-bold text-slate-700 mb-2 flex justify-between items-center">
|
|
<div>
|
|
<span class="bg-blue-100 text-blue-700 w-5 h-5 inline-flex items-center justify-center rounded-full text-xs mr-1">${index}</span>
|
|
${fieldLabel}
|
|
<span class="text-xs text-slate-400 font-mono font-normal ml-2 bg-slate-100 px-2 py-0.5 rounded-full border border-slate-200">${sectionKey}.${fieldKey}</span>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button type="button" onclick="toggleFullscreenSql(this)" class="text-xs bg-slate-50 hover:bg-slate-200 text-slate-600 font-bold py-1.5 px-3 rounded-md transition-colors flex items-center border border-slate-300" title="ขยายเต็มจอเพื่อแก้ไขสะดวกขึ้น">
|
|
<i class="fa-solid fa-expand mr-1.5 text-slate-500"></i> ขยาย
|
|
</button>
|
|
<button type="button" onclick="formatSql('${sectionKey}', '${fieldKey}')" class="text-xs bg-indigo-50 hover:bg-indigo-100 text-indigo-700 font-bold py-1.5 px-3 rounded-md transition-colors flex items-center border border-indigo-200" title="จัดรูปแบบคำสั่งให้สวยงามอ่านง่าย">
|
|
<i class="fa-solid fa-align-left mr-1.5 text-indigo-500"></i> จัดรูปแบบ
|
|
</button>
|
|
<button type="button" onclick="testSingleSql('${sectionKey}', '${fieldKey}')" class="text-xs bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold py-1.5 px-3 rounded-md transition-colors flex items-center border border-slate-300">
|
|
<i class="fa-solid fa-play mr-1.5 text-blue-500"></i> ทดสอบ
|
|
</button>
|
|
<button type="button" onclick="saveSingleSql('${sectionKey}', '${fieldKey}')" class="text-xs bg-emerald-50 hover:bg-emerald-100 text-emerald-700 font-bold py-1.5 px-3 rounded-md transition-colors flex items-center border border-emerald-200">
|
|
<i class="fa-solid fa-check mr-1.5 text-emerald-500"></i> บันทึก
|
|
</button>
|
|
</div>
|
|
</label>
|
|
${hintHtml}
|
|
<textarea id="sql-${sectionKey}-${fieldKey}" class="w-full h-32 p-3 font-mono text-sm bg-slate-50 border border-slate-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition-all shadow-inner resize-y" data-section="${sectionKey}" data-key="${fieldKey}" spellcheck="false" placeholder="SELECT ... FROM ...">${val}</textarea>
|
|
<div id="test-result-${sectionKey}-${fieldKey}" class="mt-3 hidden">
|
|
<div class="flex justify-between items-end mb-1">
|
|
<div class="text-xs font-bold text-slate-500 flex items-center"><i class="fa-solid fa-table mr-1"></i> ผลลัพธ์การทดสอบ (5 แถวแรก)</div>
|
|
<button type="button" id="copy-btn-${sectionKey}-${fieldKey}" class="text-[11px] text-blue-600 hover:text-blue-800 font-bold hidden transition-colors" onclick="copyCompiledSql(this)">
|
|
<i class="fa-solid fa-copy mr-1"></i> คัดลอกคำสั่งพร้อมตัวแปร
|
|
</button>
|
|
</div>
|
|
<div class="overflow-x-auto border border-slate-200 rounded-lg bg-white relative">
|
|
<div class="params-info bg-slate-50 border-b border-slate-200 px-3 py-2 empty:hidden"></div>
|
|
<table class="w-full text-left border-collapse text-xs whitespace-nowrap table-result">
|
|
<!-- Table content will go here -->
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
bodyDiv.appendChild(fieldDiv);
|
|
index++;
|
|
}
|
|
|
|
sectionDiv.appendChild(bodyDiv);
|
|
form.appendChild(sectionDiv);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function toggleFullscreenSql(btn) {
|
|
const container = btn.closest('.flex-col');
|
|
const textarea = container.querySelector('textarea');
|
|
|
|
const isFullscreen = container.classList.contains('fixed');
|
|
|
|
if (isFullscreen) {
|
|
// Restore normal
|
|
container.classList.remove('fixed', 'inset-4', 'z-[100]', 'shadow-2xl', 'overflow-y-auto');
|
|
btn.innerHTML = '<i class="fa-solid fa-expand mr-1.5 text-slate-500"></i> ขยาย';
|
|
textarea.classList.remove('h-[calc(100vh-200px)]');
|
|
textarea.classList.add('h-32');
|
|
document.body.style.overflow = '';
|
|
} else {
|
|
// Fullscreen
|
|
container.classList.add('fixed', 'inset-4', 'z-[100]', 'shadow-2xl', 'overflow-y-auto');
|
|
btn.innerHTML = '<i class="fa-solid fa-compress mr-1.5 text-slate-500"></i> ย่อจอ';
|
|
textarea.classList.remove('h-32');
|
|
textarea.classList.add('h-[calc(100vh-200px)]');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
}
|
|
|
|
function formatSql(section, key) {
|
|
const textarea = document.getElementById(`sql-${section}-${key}`);
|
|
if (!textarea) return;
|
|
|
|
let sqlText = textarea.value;
|
|
if (!sqlText.trim()) return;
|
|
|
|
try {
|
|
if (typeof sqlFormatter !== 'undefined') {
|
|
// หลีกเลี่ยงปัญหา parser ไม่รู้จักตัวแปร :start ของ PDO
|
|
// โดยการแปลง :variable เป็นข้อความธรรมดาก่อน (เช่น __PDO_variable__)
|
|
let safeSql = sqlText.replace(/:([a-zA-Z0-9_]+)/g, '__PDO_$1__');
|
|
|
|
let formatted = sqlFormatter.format(safeSql, {
|
|
language: 'mysql',
|
|
tabWidth: 4,
|
|
keywordCase: 'upper',
|
|
linesBetweenQueries: 2
|
|
});
|
|
|
|
// แปลงกลับคืนมาเป็น :variable เหมือนเดิม
|
|
formatted = formatted.replace(/__PDO_([a-zA-Z0-9_]+)__/gi, ':$1');
|
|
|
|
textarea.value = formatted;
|
|
|
|
const Toast = Swal.mixin({
|
|
toast: true,
|
|
position: 'top-end',
|
|
showConfirmButton: false,
|
|
timer: 1500
|
|
});
|
|
Toast.fire({
|
|
icon: 'success',
|
|
title: 'จัดรูปแบบเรียบร้อย'
|
|
});
|
|
} else {
|
|
console.warn('sql-formatter library not loaded');
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
Swal.fire({
|
|
title: 'ไม่สามารถจัดรูปแบบได้',
|
|
text: 'อาจมีไวยากรณ์คำสั่ง SQL ที่ไม่สมบูรณ์',
|
|
icon: 'warning',
|
|
confirmButtonText: 'ตกลง'
|
|
});
|
|
}
|
|
}
|
|
|
|
function testSingleSql(section, key) {
|
|
const textarea = document.getElementById(`sql-${section}-${key}`);
|
|
if (!textarea) return;
|
|
|
|
const sqlText = textarea.value;
|
|
if (!sqlText.trim()) {
|
|
Swal.fire({
|
|
title: 'ข้อมูลไม่ครบถ้วน',
|
|
text: 'กรุณาป้อนคำสั่ง SQL ก่อนกดทดสอบ',
|
|
icon: 'warning',
|
|
confirmButtonText: 'ตกลง',
|
|
confirmButtonColor: '#3b82f6'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const resultContainer = document.getElementById(`test-result-${section}-${key}`);
|
|
const tableBody = resultContainer.querySelector('table');
|
|
const paramsInfo = resultContainer.querySelector('.params-info');
|
|
const copyBtn = document.getElementById(`copy-btn-${section}-${key}`);
|
|
|
|
resultContainer.classList.remove('hidden');
|
|
paramsInfo.innerHTML = '';
|
|
if (copyBtn) copyBtn.classList.add('hidden');
|
|
|
|
tableBody.innerHTML = '<tr><td class="p-4 text-center text-slate-500"><i class="fa-solid fa-spinner fa-spin mr-2"></i> กำลังทดสอบคำสั่ง...</td></tr>';
|
|
|
|
fetch('?ajax_test_sql=1', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ sql_text_b64: btoa(encodeURIComponent(sqlText)) })
|
|
})
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
if (res.status === 'success') {
|
|
if (copyBtn && res.sql_compiled) {
|
|
copyBtn.classList.remove('hidden');
|
|
copyBtn.setAttribute('data-sql', res.sql_compiled);
|
|
}
|
|
|
|
// Show params used
|
|
if (res.params_used && Object.keys(res.params_used).length > 0) {
|
|
let pList = Object.entries(res.params_used).map(([k,v]) => `<span class="inline-block bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded font-mono text-[10px] border border-blue-200 mr-2">:${k} = '${v}'</span>`).join('');
|
|
paramsInfo.innerHTML = `<span class="text-[10px] font-bold text-slate-500 mr-2">ตัวแปรวันที่ที่ใช้:</span>${pList}`;
|
|
} else {
|
|
paramsInfo.innerHTML = '';
|
|
}
|
|
|
|
if (res.data.length === 0) {
|
|
tableBody.innerHTML = '<tr><td class="p-3 text-center text-slate-500">ทดสอบสำเร็จ แต่ไม่พบข้อมูลจากช่วงวันที่ปัจจุบัน</td></tr>';
|
|
return;
|
|
}
|
|
|
|
// Build Table
|
|
const columns = Object.keys(res.data[0]);
|
|
let thead = '<thead><tr class="bg-slate-100 text-slate-600 uppercase text-[10px]">';
|
|
columns.forEach(col => {
|
|
thead += `<th class="px-3 py-2 border-b border-slate-200 font-bold">${col}</th>`;
|
|
});
|
|
thead += '</tr></thead>';
|
|
|
|
let tbody = '<tbody>';
|
|
res.data.forEach(row => {
|
|
tbody += '<tr class="border-b border-slate-100 hover:bg-slate-50">';
|
|
columns.forEach(col => {
|
|
tbody += `<td class="px-3 py-2 text-slate-700">${row[col] !== null ? row[col] : '<em>null</em>'}</td>`;
|
|
});
|
|
tbody += '</tr>';
|
|
});
|
|
tbody += '</tbody>';
|
|
|
|
tableBody.innerHTML = thead + tbody;
|
|
} else {
|
|
tableBody.innerHTML = `<tr><td class="p-3 text-red-500 font-mono text-xs whitespace-normal break-words">${res.message}</td></tr>`;
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
tableBody.innerHTML = '<tr><td class="p-3 text-red-500">เกิดข้อผิดพลาดในการเชื่อมต่อเซิร์ฟเวอร์</td></tr>';
|
|
});
|
|
}
|
|
|
|
function saveSingleSql(section, key) {
|
|
const textarea = document.getElementById(`sql-${section}-${key}`);
|
|
if (!textarea) return;
|
|
|
|
const sqlText = textarea.value;
|
|
if (!sqlText.trim()) {
|
|
Swal.fire({
|
|
title: 'ข้อมูลไม่ครบถ้วน',
|
|
text: 'คำสั่ง SQL ห้ามเป็นค่าว่าง',
|
|
icon: 'warning',
|
|
confirmButtonText: 'ตกลง',
|
|
confirmButtonColor: '#3b82f6'
|
|
});
|
|
return;
|
|
}
|
|
|
|
Swal.fire({
|
|
title: 'ยืนยันการบันทึก?',
|
|
html: `ต้องการบันทึกคำสั่ง <b class="text-blue-600">${section}.${key}</b> ใช่หรือไม่?`,
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#10b981',
|
|
cancelButtonColor: '#64748b',
|
|
confirmButtonText: '<i class="fa-solid fa-check mr-1"></i> บันทึก',
|
|
cancelButtonText: 'ยกเลิก'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
fetch('?ajax_save_single_sql=1', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ section: section, key: key, sql_text_b64: btoa(encodeURIComponent(sqlText)) })
|
|
})
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
if (res.status === 'success') {
|
|
// Toast notification for success
|
|
const Toast = Swal.mixin({
|
|
toast: true,
|
|
position: 'top-end',
|
|
showConfirmButton: false,
|
|
timer: 2500,
|
|
timerProgressBar: true
|
|
});
|
|
Toast.fire({
|
|
icon: 'success',
|
|
title: 'บันทึกคำสั่งเรียบร้อยแล้ว'
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
title: 'บันทึกไม่สำเร็จ',
|
|
text: res.message,
|
|
icon: 'error',
|
|
confirmButtonText: 'ตกลง',
|
|
confirmButtonColor: '#ef4444'
|
|
});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
Swal.fire({
|
|
title: 'เกิดข้อผิดพลาด',
|
|
text: 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้',
|
|
icon: 'error',
|
|
confirmButtonText: 'ตกลง',
|
|
confirmButtonColor: '#ef4444'
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function copyCompiledSql(btn) {
|
|
const sql = btn.getAttribute('data-sql');
|
|
if (!sql) return;
|
|
|
|
navigator.clipboard.writeText(sql).then(() => {
|
|
const originalHTML = btn.innerHTML;
|
|
btn.innerHTML = '<i class="fa-solid fa-check mr-1 text-emerald-600"></i> คัดลอกแล้ว';
|
|
setTimeout(() => {
|
|
btn.innerHTML = originalHTML;
|
|
}, 2000);
|
|
}).catch(err => {
|
|
console.error('Failed to copy text: ', err);
|
|
const Toast = Swal.mixin({
|
|
toast: true,
|
|
position: 'top-end',
|
|
showConfirmButton: false,
|
|
timer: 3000
|
|
});
|
|
Toast.fire({
|
|
icon: 'error',
|
|
title: 'ไม่สามารถคัดลอกข้อความได้'
|
|
});
|
|
});
|
|
}
|
|
|
|
let isSettingsUnlocked = false;
|
|
let pendingSettingsTab = null;
|
|
|
|
function promptSettingsPassword() {
|
|
Swal.fire({
|
|
title: 'เข้าสู่หน้าตั้งค่า',
|
|
text: 'กรุณากรอกรหัสผ่านเพื่อเข้าถึงการตั้งค่า',
|
|
input: 'password',
|
|
inputPlaceholder: 'รหัสผ่าน',
|
|
inputAttributes: {
|
|
autocapitalize: 'off',
|
|
autocorrect: 'off'
|
|
},
|
|
showCancelButton: true,
|
|
confirmButtonText: '<i class="fa-solid fa-unlock mr-1"></i> ปลดล็อค',
|
|
cancelButtonText: 'ยกเลิก',
|
|
confirmButtonColor: '#3b82f6',
|
|
showLoaderOnConfirm: true,
|
|
allowOutsideClick: () => !Swal.isLoading(),
|
|
preConfirm: (password) => {
|
|
if (!password) {
|
|
Swal.showValidationMessage('กรุณากรอกรหัสผ่าน');
|
|
return false;
|
|
}
|
|
return fetch('?ajax_auth_settings=1', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ password: password })
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) throw new Error(response.statusText);
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status !== 'success') {
|
|
Swal.showValidationMessage(data.message || 'รหัสผ่านไม่ถูกต้อง');
|
|
return false;
|
|
}
|
|
return data;
|
|
})
|
|
.catch(error => {
|
|
Swal.showValidationMessage(`การเชื่อมต่อล้มเหลว: ${error}`);
|
|
});
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
isSettingsUnlocked = true;
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'ปลดล็อคสำเร็จ',
|
|
toast: true,
|
|
position: 'top-end',
|
|
showConfirmButton: false,
|
|
timer: 1500
|
|
});
|
|
if(pendingSettingsTab) {
|
|
window.switchTab('tab-settings', pendingSettingsTab.element, pendingSettingsTab.titleText);
|
|
pendingSettingsTab = null;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Hook into tab switching to auto-load config when settings tab is opened
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (typeof window.switchTab === 'function') {
|
|
const originalSwitchTab = window.switchTab;
|
|
window.switchTab = function(tabId, element, titleText) {
|
|
if(tabId === 'tab-settings' && !isSettingsUnlocked) {
|
|
pendingSettingsTab = { element: element, titleText: titleText };
|
|
promptSettingsPassword();
|
|
return;
|
|
}
|
|
originalSwitchTab(tabId, element, titleText);
|
|
if(tabId === 'tab-settings') {
|
|
loadSqlConfig();
|
|
}
|
|
};
|
|
}
|
|
});
|
|
</script>
|