Files
gravity/บริหารจัดการงานเปล/views/settings/index.php
T
2026-09-16 23:20:08 +07:00

899 lines
53 KiB
PHP

<?php
// views/settings/index.php
require_once dirname(__DIR__, 2) . '/models/Setting.php';
require_once dirname(__DIR__, 2) . '/models/Department.php';
$settingModel = new Setting();
$deptModel = new Department();
$flatSettings = $settingModel->getAllFlat();
// Handle Department Actions
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'department') {
if (ob_get_length()) ob_clean();
header('Content-Type: application/json');
try {
if ($_POST['sub_action'] === 'add') {
$deptModel->add($_POST['name']);
echo json_encode(['status' => 'success', 'message' => 'เพิ่มจุดรับ-ส่งเรียบร้อยแล้ว']);
} elseif ($_POST['sub_action'] === 'edit') {
$deptModel->update($_POST['id'], $_POST['name'], $_POST['status']);
echo json_encode(['status' => 'success', 'message' => 'แก้ไขจุดรับ-ส่งเรียบร้อยแล้ว']);
} elseif ($_POST['sub_action'] === 'delete') {
$deptModel->delete($_POST['id']);
echo json_encode(['status' => 'success', 'message' => 'ลบจุดรับ-ส่งเรียบร้อยแล้ว']);
}
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_settings') {
if (ob_get_length()) ob_clean(); // Clear any HTML output buffered by index.php
header('Content-Type: application/json');
try {
if (isset($_POST['settings']) && is_array($_POST['settings'])) {
foreach ($_POST['settings'] as $key => $value) {
$settingModel->set($key, $value);
}
}
echo json_encode(['status' => 'success', 'message' => 'บันทึกการตั้งค่าเรียบร้อยแล้ว']);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
exit;
}
?>
<div class="mb-8">
<h2 class="text-3xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-emerald-600 to-teal-400 dark:from-emerald-400 dark:to-teal-200 mb-2">ตั้งค่าระบบ (System Settings)</h2>
<p class="text-slate-500 dark:text-slate-400 text-lg">จัดการข้อมูลหน่วยงาน การแจ้งเตือน และการจ่ายงาน</p>
</div>
<?php
// Prepare SweetAlert message if saved
$swal_msg = '';
if (isset($_GET['saved'])) {
if ($_GET['saved'] === 'dept_add') $swal_msg = 'เพิ่มจุดรับ-ส่งเรียบร้อยแล้ว';
elseif ($_GET['saved'] === 'dept_edit') $swal_msg = 'แก้ไขจุดรับ-ส่งเรียบร้อยแล้ว';
elseif ($_GET['saved'] === 'dept_delete') $swal_msg = 'ลบจุดรับ-ส่งเรียบร้อยแล้ว';
else $swal_msg = 'บันทึกการตั้งค่าเรียบร้อยแล้ว';
}
?>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6 pb-10">
<!-- Sidebar Navigation -->
<div class="lg:col-span-1">
<div class="glass-card sticky top-24 overflow-hidden border border-white/40 dark:border-slate-700/50 shadow-xl shadow-emerald-900/5 rounded-2xl">
<nav class="flex flex-col text-sm font-medium p-2 space-y-1" id="settingsTabs">
<a href="#tab-general" class="tab-link px-4 py-3 rounded-xl text-emerald-700 dark:text-emerald-300 bg-emerald-100/50 dark:bg-emerald-900/30 font-bold transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-emerald-100 dark:bg-emerald-800/50 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-hospital text-emerald-600 dark:text-emerald-400"></i>
</div>
ข้อมูลหน่วยงาน
</a>
<a href="#tab-line" class="tab-link px-4 py-3 rounded-xl text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-slate-200 transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-brands fa-line text-green-500"></i>
</div>
LINE Notify / Bot
</a>
<a href="#tab-telegram" class="tab-link px-4 py-3 rounded-xl text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-slate-200 transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-brands fa-telegram text-blue-500"></i>
</div>
Telegram Bot
</a>
<a href="#tab-dispatch" class="tab-link px-4 py-3 rounded-xl text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-slate-200 transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-robot text-purple-500"></i>
</div>
ระบบจ่ายงานอัตโนมัติ
</a>
<a href="#tab-equipments" class="tab-link px-4 py-3 rounded-xl text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-slate-200 transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-wheelchair text-emerald-500"></i>
</div>
ประเภทอุปกรณ์
</a>
<a href="#tab-priorities" class="tab-link px-4 py-3 rounded-xl text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-slate-200 transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-triangle-exclamation text-red-500"></i>
</div>
ระดับความฉุกเฉิน
</a>
<a href="#tab-points" class="tab-link px-4 py-3 rounded-xl text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-slate-200 transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-map-location-dot text-rose-500"></i>
</div>
จุดรับ-ส่ง (จุดบริการ)
</a>
<a href="#tab-maps" class="tab-link px-4 py-3 rounded-xl text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-900 dark:hover:text-slate-200 transition-all flex items-center group">
<div class="w-8 h-8 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center mr-3 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-map text-amber-500"></i>
</div>
แผนผังอาคาร (Floor Plans)
</a>
</nav>
</div>
</div>
<!-- Settings Content -->
<div class="lg:col-span-3 relative min-h-[500px]">
<form id="mainSettingsForm">
<input type="hidden" name="action" value="save_settings">
<input type="hidden" name="csrf_token" value="<?= generateCSRFToken() ?>">
<!-- General Settings -->
<div id="tab-general" class="glass-card p-6 tab-content">
<h3 class="text-lg font-bold text-slate-800 dark:text-white mb-4 flex items-center">
<i class="fa-solid fa-hospital text-emerald-500 mr-2"></i> ข้อมูลหน่วยงาน
</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อโรงพยาบาล</label>
<input type="text" class="glass-input w-full px-4 py-2" name="settings[hospital_name]" value="<?= escape($settings['general']['hospital_name'] ?? 'โรงพยาบาลเกาะสมุย') ?>">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อหน่วยงาน</label>
<input type="text" class="glass-input w-full px-4 py-2" name="settings[department_name]" value="<?= escape($settings['general']['department_name'] ?? 'ศูนย์เปล (Patient Transport)') ?>">
</div>
</div>
</div>
<!-- LINE Settings -->
<div id="tab-line" class="glass-card p-6 tab-content hidden">
<h3 class="text-lg font-bold text-slate-800 dark:text-white mb-4 flex items-center">
<i class="fa-brands fa-line text-green-500 mr-2"></i> LINE Notify / Bot
</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">LINE Notify Token</label>
<div class="relative">
<input type="password" class="glass-input w-full px-4 py-2 pr-10" name="settings[line_notify_token]" value="<?= escape($flatSettings['line_notify_token'] ?? '') ?>">
<button type="button" class="absolute inset-y-0 right-0 px-3 flex items-center text-slate-400 hover:text-slate-600 toggle-password">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">LINE Group ID</label>
<input type="text" class="glass-input w-full px-4 py-2" name="settings[line_group_id]" value="<?= escape($flatSettings['line_group_id'] ?? '') ?>">
</div>
</div>
</div>
<!-- Telegram Settings -->
<div id="tab-telegram" class="glass-card p-6 tab-content hidden">
<h3 class="text-lg font-bold text-slate-800 dark:text-white mb-4 flex items-center">
<i class="fa-brands fa-telegram text-blue-500 mr-2"></i> Telegram Bot
</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Telegram Bot Token</label>
<div class="relative">
<input type="password" class="glass-input w-full px-4 py-2 pr-10" name="settings[telegram_bot_token]" value="<?= escape($flatSettings['telegram_bot_token'] ?? '') ?>">
<button type="button" class="absolute inset-y-0 right-0 px-3 flex items-center text-slate-400 hover:text-slate-600 toggle-password">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Telegram Chat ID</label>
<input type="text" class="glass-input w-full px-4 py-2" name="settings[telegram_chat_id]" value="<?= escape($flatSettings['telegram_chat_id'] ?? '') ?>">
</div>
</div>
</div>
<!-- Dispatch Settings -->
<div id="tab-dispatch" class="glass-card p-6 tab-content hidden">
<h3 class="text-lg font-bold text-slate-800 dark:text-white mb-4 flex items-center">
<i class="fa-solid fa-robot text-purple-500 mr-2"></i> ระบบจ่ายงานอัตโนมัติ
</h3>
<div class="space-y-4">
<div class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-800/50 rounded-xl border border-slate-200 dark:border-slate-700">
<div>
<p class="font-medium text-slate-800 dark:text-white">เปิดใช้งาน Smart Auto Dispatch</p>
<p class="text-sm text-slate-500">ระบบจะค้นหาและจ่ายงานให้เจ้าหน้าที่ที่ว่างและอยู่ใกล้ที่สุดอัตโนมัติ</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="hidden" name="settings[auto_dispatch_enabled]" value="0">
<input type="checkbox" name="settings[auto_dispatch_enabled]" value="1" class="sr-only peer" <?= (isset($flatSettings['auto_dispatch_enabled']) && $flatSettings['auto_dispatch_enabled'] == '1') ? 'checked' : '' ?>>
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-emerald-500"></div>
</label>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">เวลามาตรฐาน SLA สำหรับงานส่งผู้ป่วย (นาที)</label>
<input type="number" class="glass-input w-full px-4 py-2" name="settings[sla_minutes_default]" value="<?= escape($flatSettings['sla_minutes_default'] ?? '15') ?>">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">น้ำหนักระยะทาง (กม.) ในการพิจารณา</label>
<p class="text-xs text-slate-500 mb-2">ระยะทางสูงสุดที่จะนำมาคำนวณการจ่ายงานอัตโนมัติ</p>
<input type="number" step="0.1" class="glass-input w-full px-4 py-2" name="settings[dispatch_max_distance]" value="<?= escape($flatSettings['dispatch_max_distance'] ?? '2.0') ?>">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ความสำคัญของระยะทาง (คะแนน)</label>
<p class="text-xs text-slate-500 mb-2">คะแนนเพิ่มสำหรับเจ้าหน้าที่ที่อยู่ใกล้ที่สุด (0-100)</p>
<input type="number" class="glass-input w-full px-4 py-2" name="settings[dispatch_distance_weight]" value="<?= escape($flatSettings['dispatch_distance_weight'] ?? '50') ?>">
</div>
</div>
</div>
<!-- Equipments Settings -->
<div id="tab-equipments" class="glass-card p-6 tab-content hidden">
<h3 class="text-lg font-bold text-slate-800 dark:text-white mb-4 flex items-center justify-between">
<span><i class="fa-solid fa-wheelchair text-emerald-500 mr-2"></i> ประเภทอุปกรณ์</span>
<button type="button" class="btn-secondary-glass py-1 px-3 text-sm" onclick="addEquipmentItem()">
<i class="fa-solid fa-plus me-1"></i> เพิ่มอุปกรณ์
</button>
</h3>
<div class="space-y-4">
<p class="text-sm text-slate-500 mb-2">กำหนดประเภทอุปกรณ์ที่สามารถเลือกได้ในหน้าแบบฟอร์มร้องของาน</p>
<input type="hidden" name="settings[equipment_types]" id="equipmentTypesJson">
<div id="equipmentList" class="space-y-3">
<!-- Dynamic content via JS -->
</div>
</div>
</div>
<!-- Priorities Settings -->
<div id="tab-priorities" class="glass-card p-6 tab-content hidden">
<h3 class="text-lg font-bold text-slate-800 dark:text-white mb-4 flex items-center justify-between">
<span><i class="fa-solid fa-triangle-exclamation text-red-500 mr-2"></i> ระดับความฉุกเฉิน</span>
<button type="button" class="btn-secondary-glass py-1 px-3 text-sm" onclick="addPriorityItem()">
<i class="fa-solid fa-plus me-1"></i> เพิ่มความฉุกเฉิน
</button>
</h3>
<div class="space-y-4">
<p class="text-sm text-slate-500 mb-2">กำหนดระดับความฉุกเฉินและสีที่จะใช้แสดงผล</p>
<input type="hidden" name="settings[job_priorities]" id="jobPrioritiesJson">
<div id="priorityList" class="space-y-3">
<!-- Dynamic content via JS -->
</div>
</div>
</div>
<!-- Save Button Area -->
<div class="sticky bottom-4 z-10 p-4 mt-6 flex justify-end bg-white/70 dark:bg-slate-900/70 backdrop-blur-md rounded-2xl border border-emerald-500/20 shadow-[0_8px_30px_rgb(0,0,0,0.12)]">
<button type="submit" class="btn-primary-glass py-2 px-8 font-medium text-lg rounded-xl flex items-center hover:scale-105 transition-transform duration-200">
<i class="fa-solid fa-save me-2"></i> บันทึกการตั้งค่า
</button>
</div>
</form>
<!-- Tab Content for Points (Outside the main form so it can have its own forms) -->
<div id="tab-points" class="tab-content hidden animate-[fadeUp_0.3s_ease-out]">
<div class="glass-card p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-slate-800 dark:text-white flex items-center">
<i class="fa-solid fa-map-location-dot text-rose-500 mr-2"></i> จุดรับ-ส่ง (จุดบริการ)
</h3>
<button type="button" onclick="openAddPointModal()" class="btn-primary-glass py-1 px-4 text-sm font-medium">
<i class="fa-solid fa-plus me-1"></i> เพิ่มจุดรับ-ส่ง
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider border-b border-slate-200 dark:border-slate-700">
<th class="pb-3 px-4">ชื่อจุดบริการ</th>
<th class="pb-3 px-4 text-center">สถานะ</th>
<th class="pb-3 px-4 text-right">จัดการ</th>
</tr>
</thead>
<tbody class="text-sm text-slate-700 dark:text-slate-300 divide-y divide-slate-200 dark:divide-slate-700">
<?php
$departments = $deptModel->getAll();
foreach ($departments as $dept):
?>
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
<td class="py-3 px-4 font-medium"><?= escape($dept['name']) ?></td>
<td class="py-3 px-4 text-center">
<?php if ($dept['status'] === 'active'): ?>
<span class="bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs font-medium">เปิดใช้งาน</span>
<?php else: ?>
<span class="bg-slate-100 text-slate-800 px-2 py-1 rounded-full text-xs font-medium">ปิดใช้งาน</span>
<?php endif; ?>
</td>
<td class="py-3 px-4 text-right">
<a href="?page=public_request&dept_id=<?= $dept['id'] ?>" target="_blank" class="text-emerald-500 hover:text-emerald-700 mr-3" title="ดู/พิมพ์ QR Code (หน้าสแกนเรียกเวรเปล)">
<i class="fa-solid fa-qrcode"></i>
</a>
<button type="button" onclick="openEditPointModal(<?= $dept['id'] ?>, '<?= escape(addslashes($dept['name'])) ?>', '<?= $dept['status'] ?>')" class="text-blue-500 hover:text-blue-700 mr-3" title="แก้ไข">
<i class="fa-solid fa-edit"></i>
</button>
<form class="inline dept-delete-form">
<input type="hidden" name="csrf_token" value="<?= generateCSRFToken() ?>">
<input type="hidden" name="action" value="department">
<input type="hidden" name="sub_action" value="delete">
<input type="hidden" name="id" value="<?= $dept['id'] ?>">
<button type="submit" class="text-red-500 hover:text-red-700"><i class="fa-solid fa-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Add/Edit Point Modal -->
<div id="pointModal" class="fixed inset-0 z-50 hidden bg-slate-900/50 backdrop-blur-sm flex items-center justify-center">
<div class="glass-card w-full max-w-md p-6 transform transition-all">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-slate-800 dark:text-white" id="pointModalTitle">เพิ่มจุดรับ-ส่ง</h3>
<button type="button" onclick="closePointModal()" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
<i class="fa-solid fa-times fa-lg"></i>
</button>
</div>
<form id="deptModalForm">
<input type="hidden" name="csrf_token" value="<?= generateCSRFToken() ?>">
<input type="hidden" name="action" value="department">
<input type="hidden" name="sub_action" id="pointSubAction" value="add">
<input type="hidden" name="id" id="pointId" value="">
<div class="space-y-4 mb-6">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อจุดบริการ <span class="text-red-500">*</span></label>
<input type="text" id="pointName" name="name" class="glass-input w-full px-4 py-2" required>
</div>
<div id="statusContainer" class="hidden">
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">สถานะ</label>
<select id="pointStatus" name="status" class="glass-input w-full px-4 py-2">
<option value="active">เปิดใช้งาน</option>
<option value="inactive">ปิดใช้งาน</option>
</select>
</div>
</div>
<div class="flex justify-end gap-3">
<button type="button" onclick="closePointModal()" class="btn-secondary-glass py-2 px-4">ยกเลิก</button>
<button type="submit" class="btn-primary-glass py-2 px-6">บันทึก</button>
</div>
</form>
</div>
</div>
<!-- Tab Content for Maps -->
<div id="tab-maps" class="tab-content hidden animate-[fadeUp_0.3s_ease-out]">
<div class="glass-card p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-slate-800 dark:text-white flex items-center">
<i class="fa-solid fa-map text-amber-500 mr-2"></i> แผนผังอาคาร
</h3>
<button type="button" onclick="openMapUploadModal()" class="btn-primary-glass py-1 px-4 text-sm font-medium">
<i class="fa-solid fa-plus me-1"></i> เพิ่มแผนผัง
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="mapsGrid">
<!-- Dynamic Map Cards -->
<div class="col-span-full text-center text-slate-500 py-8">กำลังโหลดข้อมูลแผนผัง...</div>
</div>
</div>
</div>
<!-- Map Upload Modal -->
<div id="mapUploadModal" class="fixed inset-0 z-50 hidden bg-slate-900/50 backdrop-blur-sm flex items-center justify-center p-4">
<div class="glass-card w-full max-w-md p-6 transform transition-all">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-slate-800 dark:text-white">เพิ่มแผนผังอาคาร</h3>
<button type="button" onclick="closeMapUploadModal()" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
<i class="fa-solid fa-times fa-lg"></i>
</button>
</div>
<form id="mapUploadForm">
<input type="hidden" name="action" value="upload">
<div class="space-y-4 mb-6">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชื่อแผนผัง <span class="text-red-500">*</span></label>
<input type="text" name="name" class="glass-input w-full px-4 py-2" required placeholder="เช่น ชั้น 1 อาคารผู้ป่วยนอก">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ตึก/อาคาร <span class="text-red-500">*</span></label>
<input type="text" name="building" class="glass-input w-full px-4 py-2" required>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ชั้น <span class="text-red-500">*</span></label>
<input type="text" name="floor" class="glass-input w-full px-4 py-2" required>
</div>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ตำแหน่งศูนย์เปล/จุดพักคอย (ถ้ามี)</label>
<input type="text" name="transport_center_location" class="glass-input w-full px-4 py-2" placeholder="เช่น บริเวณหน้าลิฟต์ฝั่งตะวันออก">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">ไฟล์รูปภาพแผนผัง <span class="text-red-500">*</span></label>
<input type="file" name="map_image" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-emerald-50 file:text-emerald-700 hover:file:bg-emerald-100" accept="image/jpeg, image/png, image/webp" required>
</div>
</div>
<div class="flex justify-end gap-3">
<button type="button" onclick="closeMapUploadModal()" class="btn-secondary-glass py-2 px-4">ยกเลิก</button>
<button type="submit" class="btn-primary-glass py-2 px-6">อัปโหลด</button>
</div>
</form>
</div>
</div>
<script>
// Data from server
let equipmentTypesData = <?= isset($settings['general']['equipment_types']) && !empty($settings['general']['equipment_types']) ? $settings['general']['equipment_types'] : '[{"id":"wheelchair","name":"รถนั่ง (Wheelchair)","icon":"fa-wheelchair"},{"id":"stretcher","name":"รถนอน (Stretcher)","icon":"fa-bed"},{"id":"bed","name":"เตียง (Bed)","icon":"fa-bed-pulse"}]' ?>;
let jobPrioritiesData = <?= isset($settings['general']['job_priorities']) && !empty($settings['general']['job_priorities']) ? $settings['general']['job_priorities'] : '[{"id":"normal","name":"ทั่วไป (Normal)","color":"blue"},{"id":"urgent","name":"ด่วน (Urgency)","color":"amber"},{"id":"emergency","name":"ฉุกเฉิน (Emergency)","color":"red"}]' ?>;
// Ensure data is array if it's string
if (typeof equipmentTypesData === 'string') {
try { equipmentTypesData = JSON.parse(equipmentTypesData); } catch (e) { equipmentTypesData = []; }
}
if (typeof jobPrioritiesData === 'string') {
try { jobPrioritiesData = JSON.parse(jobPrioritiesData); } catch (e) { jobPrioritiesData = []; }
}
// Equipment Logic
function renderEquipments() {
const container = document.getElementById('equipmentList');
container.innerHTML = '';
equipmentTypesData.forEach((item, index) => {
const html = `
<div class="flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-800/50 rounded-xl border border-slate-200 dark:border-slate-700">
<div class="w-10 text-center text-slate-400"><i class="fa-solid ${item.icon} text-lg"></i></div>
<div class="flex-1 grid grid-cols-3 gap-3">
<input type="text" class="glass-input px-3 py-1 text-sm" placeholder="ID (เช่น wheelchair)" value="${item.id}" onchange="updateEquipment(${index}, 'id', this.value)">
<input type="text" class="glass-input px-3 py-1 text-sm" placeholder="ชื่อ (เช่น รถนั่ง)" value="${item.name}" onchange="updateEquipment(${index}, 'name', this.value)">
<input type="text" class="glass-input px-3 py-1 text-sm" placeholder="Icon (เช่น fa-wheelchair)" value="${item.icon}" onchange="updateEquipment(${index}, 'icon', this.value)">
</div>
<button type="button" class="text-red-500 hover:text-red-700 p-2" onclick="removeEquipment(${index})"><i class="fa-solid fa-trash"></i></button>
</div>`;
container.insertAdjacentHTML('beforeend', html);
});
document.getElementById('equipmentTypesJson').value = JSON.stringify(equipmentTypesData);
}
window.addEquipmentItem = function() {
equipmentTypesData.push({id: '', name: '', icon: 'fa-box'});
renderEquipments();
};
window.removeEquipment = function(index) {
equipmentTypesData.splice(index, 1);
renderEquipments();
};
window.updateEquipment = function(index, field, value) {
equipmentTypesData[index][field] = value;
document.getElementById('equipmentTypesJson').value = JSON.stringify(equipmentTypesData);
if(field === 'icon') renderEquipments(); // re-render to update icon preview
};
// Priority Logic
function renderPriorities() {
const container = document.getElementById('priorityList');
container.innerHTML = '';
jobPrioritiesData.forEach((item, index) => {
const html = `
<div class="flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-800/50 rounded-xl border border-slate-200 dark:border-slate-700">
<div class="w-10 text-center text-${item.color}-500"><i class="fa-solid fa-circle text-lg"></i></div>
<div class="flex-1 grid grid-cols-3 gap-3">
<input type="text" class="glass-input px-3 py-1 text-sm" placeholder="ID (เช่น normal)" value="${item.id}" onchange="updatePriority(${index}, 'id', this.value)">
<input type="text" class="glass-input px-3 py-1 text-sm" placeholder="ชื่อ (เช่น ทั่วไป)" value="${item.name}" onchange="updatePriority(${index}, 'name', this.value)">
<select class="glass-input px-3 py-1 text-sm" onchange="updatePriority(${index}, 'color', this.value)">
<option value="blue" ${item.color==='blue'?'selected':''}>Blue (ฟ้า)</option>
<option value="emerald" ${item.color==='emerald'?'selected':''}>Emerald (เขียว)</option>
<option value="amber" ${item.color==='amber'?'selected':''}>Amber (เหลือง)</option>
<option value="orange" ${item.color==='orange'?'selected':''}>Orange (ส้ม)</option>
<option value="red" ${item.color==='red'?'selected':''}>Red (แดง)</option>
<option value="purple" ${item.color==='purple'?'selected':''}>Purple (ม่วง)</option>
<option value="slate" ${item.color==='slate'?'selected':''}>Slate (เทา)</option>
</select>
</div>
<button type="button" class="text-red-500 hover:text-red-700 p-2" onclick="removePriority(${index})"><i class="fa-solid fa-trash"></i></button>
</div>`;
container.insertAdjacentHTML('beforeend', html);
});
document.getElementById('jobPrioritiesJson').value = JSON.stringify(jobPrioritiesData);
}
window.addPriorityItem = function() {
jobPrioritiesData.push({id: '', name: '', color: 'blue'});
renderPriorities();
};
window.removePriority = function(index) {
jobPrioritiesData.splice(index, 1);
renderPriorities();
};
window.updatePriority = function(index, field, value) {
jobPrioritiesData[index][field] = value;
document.getElementById('jobPrioritiesJson').value = JSON.stringify(jobPrioritiesData);
if(field === 'color') renderPriorities(); // re-render to update color preview
};
// Initialize
renderEquipments();
renderPriorities();
document.addEventListener('DOMContentLoaded', function() {
// Password Toggle
document.querySelectorAll('.toggle-password').forEach(btn => {
btn.addEventListener('click', function() {
const input = this.previousElementSibling;
const icon = this.querySelector('i');
if (input.type === 'password') {
input.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
input.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
});
// Tab Switching Logic
const tabLinks = document.querySelectorAll('.tab-link');
const tabContents = document.querySelectorAll('.tab-content');
tabLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
// Remove active classes from all links
tabLinks.forEach(l => {
l.classList.remove('text-emerald-700', 'dark:text-emerald-300', 'bg-emerald-100/50', 'dark:bg-emerald-900/30', 'font-bold');
l.classList.add('text-slate-600', 'dark:text-slate-400', 'hover:bg-slate-50', 'dark:hover:bg-slate-800/50', 'hover:text-slate-900', 'dark:hover:text-slate-200');
// Change icon bg back
const iconBg = l.querySelector('.w-8.h-8');
if (iconBg) {
iconBg.classList.remove('bg-emerald-100', 'dark:bg-emerald-800/50');
iconBg.classList.add('bg-slate-100', 'dark:bg-slate-800');
}
});
// Add active class to clicked link
this.classList.remove('text-slate-600', 'dark:text-slate-400', 'hover:bg-slate-50', 'dark:hover:bg-slate-800/50', 'hover:text-slate-900', 'dark:hover:text-slate-200');
this.classList.add('text-emerald-700', 'dark:text-emerald-300', 'bg-emerald-100/50', 'dark:bg-emerald-900/30', 'font-bold');
// Change icon bg to active
const iconBg = this.querySelector('.w-8.h-8');
if (iconBg) {
iconBg.classList.remove('bg-slate-100', 'dark:bg-slate-800');
iconBg.classList.add('bg-emerald-100', 'dark:bg-emerald-800/50');
}
// Hide all tab contents
tabContents.forEach(content => {
content.classList.add('hidden');
});
// Show target tab content
const targetId = this.getAttribute('href');
const targetContent = document.querySelector(targetId);
if (targetContent) {
targetContent.classList.remove('hidden');
targetContent.classList.add('animate-[fadeUp_0.3s_ease-out]');
// Save active tab to localStorage
localStorage.setItem('activeSettingsTab', targetId);
// If it's the points tab or maps tab, hide the main form's submit button section
const mainSaveBtn = document.querySelector('#mainSettingsForm .sticky.bottom-4');
if (targetId === '#tab-points' || targetId === '#tab-maps') {
if(mainSaveBtn) mainSaveBtn.classList.add('hidden');
} else {
if(mainSaveBtn) mainSaveBtn.classList.remove('hidden');
}
}
});
});
// Handle AJAX Submission for Main Settings
$('#mainSettingsForm').on('submit', function(e) {
e.preventDefault();
const btn = $(this).find('button[type="submit"]');
btn.addClass('btn-loading');
$.ajax({
url: '?page=settings',
type: 'POST',
data: $(this).serialize(),
dataType: 'json',
success: function(response) {
btn.removeClass('btn-loading');
if (response.status === 'success') {
Swal.fire({
icon: 'success',
title: 'สำเร็จ',
text: response.message,
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b',
timer: 2000,
showConfirmButton: false
});
} else {
Swal.fire('ผิดพลาด', response.message, 'error');
}
},
error: function() {
btn.removeClass('btn-loading');
Swal.fire('ผิดพลาด', 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้', 'error');
}
});
});
// Handle AJAX Submission for Department Delete
$('.dept-delete-form').on('submit', function(e) {
e.preventDefault();
Swal.fire({
title: 'ยืนยันการลบ?',
text: "คุณต้องการลบจุดบริการนี้ใช่หรือไม่",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ef4444',
cancelButtonColor: '#94a3b8',
confirmButtonText: 'ลบ',
cancelButtonText: 'ยกเลิก',
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: '?page=settings',
type: 'POST',
data: $(this).serialize(),
dataType: 'json',
success: function(response) {
if (response.status === 'success') {
Swal.fire({
icon: 'success',
title: 'สำเร็จ',
text: response.message,
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b',
timer: 1500,
showConfirmButton: false
}).then(() => {
window.location.reload();
});
} else {
Swal.fire('ผิดพลาด', response.message, 'error');
}
}
});
}
});
});
// Handle AJAX Submission for Department Modal (Add/Edit)
$('#deptModalForm').on('submit', function(e) {
e.preventDefault();
const btn = $(this).find('button[type="submit"]');
btn.addClass('btn-loading');
$.ajax({
url: '?page=settings',
type: 'POST',
data: $(this).serialize(),
dataType: 'json',
success: function(response) {
btn.removeClass('btn-loading');
if (response.status === 'success') {
closePointModal();
Swal.fire({
icon: 'success',
title: 'สำเร็จ',
text: response.message,
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b',
timer: 1500,
showConfirmButton: false
}).then(() => {
window.location.reload();
});
} else {
Swal.fire('ผิดพลาด', response.message, 'error');
}
},
error: function() {
btn.removeClass('btn-loading');
Swal.fire('ผิดพลาด', 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้', 'error');
}
});
});
// Restore active tab from localStorage
const savedTab = localStorage.getItem('activeSettingsTab');
if (savedTab) {
const linkToClick = document.querySelector(`.tab-link[href="${savedTab}"]`);
if (linkToClick) {
linkToClick.click();
}
}
// Trigger SweetAlert if saved
<?php if (!empty($swal_msg)): ?>
Swal.fire({
icon: 'success',
title: 'สำเร็จ',
text: '<?= $swal_msg ?>',
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b',
timer: 3000,
showConfirmButton: false
}).then(() => {
// Clean up URL to prevent alert on reload
window.history.replaceState(null, null, window.location.pathname + '?page=settings');
});
<?php endif; ?>
});
function openAddPointModal() {
document.getElementById('pointModalTitle').textContent = 'เพิ่มจุดรับ-ส่ง';
document.getElementById('pointSubAction').value = 'add';
document.getElementById('pointId').value = '';
document.getElementById('pointName').value = '';
document.getElementById('statusContainer').classList.add('hidden');
document.getElementById('pointModal').classList.remove('hidden');
}
function openEditPointModal(id, name, status) {
document.getElementById('pointModalTitle').textContent = 'แก้ไขจุดรับ-ส่ง';
document.getElementById('pointSubAction').value = 'edit';
document.getElementById('pointId').value = id;
document.getElementById('pointName').value = name;
document.getElementById('pointStatus').value = status;
document.getElementById('statusContainer').classList.remove('hidden');
document.getElementById('pointModal').classList.remove('hidden');
}
function closePointModal() {
document.getElementById('pointModal').classList.add('hidden');
}
// --- MAPS MANAGEMENT LOGIC ---
function loadMaps() {
fetch('api/maps.php?action=list')
.then(res => res.json())
.then(res => {
const grid = document.getElementById('mapsGrid');
if (res.status === 'success') {
if (res.data.length === 0) {
grid.innerHTML = '<div class="col-span-full text-center text-slate-500 py-8">ยังไม่มีข้อมูลแผนผังอาคาร</div>';
return;
}
let html = '';
res.data.forEach(map => {
html += `
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden group">
<div class="h-40 bg-slate-100 dark:bg-slate-900 relative overflow-hidden">
<img src="${map.image_path}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300">
<div class="absolute top-2 right-2 flex gap-1">
<span class="bg-white/90 dark:bg-slate-800/90 backdrop-blur-sm text-xs px-2 py-1 rounded-md font-bold text-slate-700 dark:text-slate-200 shadow-sm">
${map.building} ชั้น ${map.floor}
</span>
</div>
</div>
<div class="p-4">
<h4 class="font-bold text-slate-800 dark:text-white mb-1 truncate" title="${map.name}">${map.name}</h4>
<div class="text-xs text-slate-500 dark:text-slate-400 mb-3 flex items-start">
<i class="fa-solid fa-location-dot mt-0.5 text-rose-500 mr-1.5 w-3"></i>
<span class="line-clamp-2">${map.transport_center_location || '- ไม่ระบุจุดศูนย์เปล -'}</span>
</div>
<div class="flex justify-between items-center mt-4">
<span class="text-xs ${map.status === 'active' ? 'text-green-500 bg-green-50 dark:bg-green-900/20' : 'text-slate-500 bg-slate-100 dark:bg-slate-800'} px-2 py-1 rounded-full">
${map.status === 'active' ? 'ใช้งาน' : 'ไม่ใช้งาน'}
</span>
<button onclick="deleteMap(${map.id}, '${map.name}')" class="text-red-500 hover:text-red-700 bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/40 w-8 h-8 rounded-full flex items-center justify-center transition-colors">
<i class="fa-solid fa-trash text-xs"></i>
</button>
</div>
</div>
</div>`;
});
grid.innerHTML = html;
}
});
}
function openMapUploadModal() {
document.getElementById('mapUploadForm').reset();
document.getElementById('mapUploadModal').classList.remove('hidden');
}
function closeMapUploadModal() {
document.getElementById('mapUploadModal').classList.add('hidden');
}
document.getElementById('mapUploadForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const btn = this.querySelector('button[type="submit"]');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin mr-2"></i> กำลังอัปโหลด...';
btn.disabled = true;
fetch('api/maps.php', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
btn.innerHTML = originalText;
btn.disabled = false;
if (data.status === 'success') {
Swal.fire({
icon: 'success',
title: 'อัปโหลดสำเร็จ',
showConfirmButton: false,
timer: 1500
});
closeMapUploadModal();
loadMaps();
} else {
Swal.fire('ข้อผิดพลาด', data.message, 'error');
}
}).catch(err => {
btn.innerHTML = originalText;
btn.disabled = false;
Swal.fire('ข้อผิดพลาด', 'ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้', 'error');
});
});
function deleteMap(id, name) {
Swal.fire({
title: 'ยืนยันการลบแผนผัง?',
text: `ต้องการลบแผนผัง "${name}" ใช่หรือไม่? (รูปภาพจะถูกลบด้วย)`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ef4444',
cancelButtonColor: '#94a3b8',
confirmButtonText: 'ลบข้อมูล',
cancelButtonText: 'ยกเลิก',
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b'
}).then((result) => {
if (result.isConfirmed) {
const formData = new FormData();
formData.append('action', 'delete');
formData.append('id', id);
fetch('api/maps.php', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
if (data.status === 'success') {
loadMaps();
Swal.fire({
title: 'ลบสำเร็จ!',
icon: 'success',
timer: 1500,
showConfirmButton: false,
background: document.documentElement.classList.contains('dark') ? '#1e293b' : '#fff',
color: document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#1e293b'
});
} else {
Swal.fire('ข้อผิดพลาด', data.message, 'error');
}
});
}
});
}
// Initialize maps on load
document.addEventListener('DOMContentLoaded', function() {
if (window.location.hash === '#tab-maps') {
loadMaps();
}
// Also add click listener to map tab link to load data if not loaded
document.querySelector('a[href="#tab-maps"]').addEventListener('click', function() {
if(document.getElementById('mapsGrid').innerHTML.includes('กำลังโหลด')) {
loadMaps();
}
});
});
</script>