Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,394 @@
<?php
require_once 'includes/db.php';
require_once 'includes/auth.php';
requireLogin();
// Fetch stations from database
$stations = [];
try {
$stmt = $pdo->query("SELECT id, name FROM units WHERE status = 'ready' ORDER BY id ASC LIMIT 16");
$stations = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// If table doesn't exist, it will fallback to default stations
}
$stationList = [];
foreach ($stations as $station) {
$stationList[] = $station['name'];
}
$defaultStations = [
"พะงัน 2", "เกาะเต่า 2", "กู้ภัยพะงัน", "กู้ภัยสมุย", "เมืองสมุย",
"กู้ชีพละไม", "กู้ชีพหน้าเมือง", "กู้ชีพตลิ่งงาม", "โรงพยาบาลกรุงเทพสมุย",
"โรงพยาบาลไทยอินเตอร์", "โรงพยาบาลบ้านดอนอินเตอร์", "โรงพยาบาลวัฒนแพทย์สมุย",
"โรงพยาบาลเกาะสมุยอินเตอร์", "โรงพยาบาลไทยอินเตอร์เกาะพะงัน", "ER สมุย", "ทชท. ว.4"
];
if (empty($stationList)) {
$stationList = $defaultStations;
}
$currentUserFullName = isset($_SESSION['user_name']) ? $_SESSION['user_name'] : 'ผู้ทดสอบ (Admin)';
$testerFirstName = explode(' ', trim($currentUserFullName))[0];
$currentTime = date('13:58:40');
$currentDateStr = "23 กรกฎาคม 2569";
$shift = "เช้า";
$announceStart = "“เจ็บป่วยฉุกเฉิน โทร 1669 ที่นี่ศูนย์รับแจ้งเหตุนเรนทรอ่าวไทย\nขณะนี้เวลา {$currentTime} นเรนทรอ่าวไทยแจ้งลูกข่ายและในข่าย จะทำการทดสอบ ว.16\nลูกข่ายและในข่ายนเรนทรอ่าวไทย มี ว.29 ไม่เร่งด่วน ให้ ว.00\nหากมี ว.29 เร่งด่วน ให้ขออนุญาต ว.6 เข้ามาได้”";
$announceEnd = "“มีอีกหรือไม่ ลูกข่ายและในข่ายนเรนทรอ่าวไทยที่ต้องการทดสอบ ว.16 ให้ ว.6 เข้ามาได้\nว.24 นี้ [ {$currentTime} ] นเรนทรอ่าวไทยแจ้ง ว.14 ว.16\nวันนี้มีผู้ร่วมทดสอบ ว.16 จำนวน <span id=\"announce-tested\">16</span> สถานี\nนเรนทรอ่าวไทยขอขอบคุณทุกสถานีที่ร่วมทดสอบ ว.16\nแจ้งลูกข่ายและในข่ายใช้ความถี่ได้ตามปกติ\nทดสอบโดย สมุย [ {$testerFirstName} ]\nนเรนทรอ่าวไทย [ {$currentTime} ]”";
require_once 'includes/header.php';
?>
<div class="space-y-6 animate-fadeIn pb-10">
<!-- Top Announcement Box -->
<div class="bg-gradient-to-br from-blue-900 to-indigo-900 text-white rounded-2xl p-5 md:p-6 shadow-lg border border-blue-800">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2.5">
<i data-lucide="radio" class="w-5 h-5 text-blue-300 animate-pulse"></i>
<span class="text-sm font-bold uppercase tracking-wider text-blue-200">ข้อความประกาศ ว.16 - ก่อนทดสอบ</span>
</div>
<button type="button" class="flex items-center gap-1.5 bg-blue-800/80 hover:bg-blue-700 text-xs px-3 py-1.5 rounded-lg border border-blue-600/50 transition-all cursor-pointer font-medium" onclick="navigator.clipboard.writeText(`<?php echo htmlspecialchars($announceStart); ?>`); Swal.fire({title: 'สำเร็จ', text: 'คัดลอกข้อความ เรียบร้อยแล้ว', icon: 'success', timer: 1500, showConfirmButton: false});">
<i data-lucide="copy" class="w-3.5 h-3.5"></i> คัดลอกบทพูด
</button>
</div>
<div class="bg-slate-950/50 p-4 rounded-xl border border-blue-800/50 font-mono text-xs md:text-sm leading-relaxed whitespace-pre-line text-slate-100"><?php echo $announceStart; ?></div>
</div>
<!-- Main 16 Stations Table -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-200/80 overflow-hidden">
<div class="p-4 md:p-5 bg-slate-50 border-b border-slate-200/80 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h3 class="text-base font-bold text-slate-800 flex items-center gap-2">
<i data-lucide="mic" class="w-4.5 h-4.5 text-indigo-600 animate-pulse"></i> ตารางทดสอบวิทยุ (16 สถานีหลัก)
</h3>
<p class="text-xs text-slate-400 mt-0.5 font-medium">ระบุระดับสัญญาณรับส่ง (5 หมายถึงดีที่สุด, ติดต่อไม่ได้ หมายถึงสัญญาณขาดหาย)</p>
</div>
<div class="text-xs font-bold bg-indigo-50 text-indigo-700 px-3 py-1.5 rounded-lg border border-indigo-100 self-start sm:self-auto flex items-center gap-1.5">
<i data-lucide="clock" class="w-3.5 h-3.5 text-indigo-500"></i> เวร<?php echo $shift; ?> | ผู้บันทึก: <?php echo $testerFirstName; ?>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse text-xs md:text-sm" id="main-stations-table">
<thead>
<tr class="bg-slate-100/50 text-slate-600 font-bold border-b border-slate-200/80">
<th class="py-3 px-4 w-12 text-center">ลำดับ</th>
<th class="py-3 px-3 min-w-[150px]">ชื่อสถานี</th>
<th class="py-3 px-3 text-center min-w-[130px]">สัญญาณส่ง (TX)</th>
<th class="py-3 px-3 text-center min-w-[130px]">สัญญาณรับ (RX)</th>
<th class="py-3 px-3 text-center w-36">ช่องรับสัญญาณ</th>
<th class="py-3 px-3 min-w-[150px]">หมายเหตุเพิ่มเติม</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 text-slate-700 font-medium">
<?php foreach ($stationList as $index => $stationName): ?>
<tr class="hover:bg-slate-50/50 transition-colors radio-row" data-id="s_<?php echo $index + 1; ?>">
<td class="py-2.5 px-4 text-center text-slate-400 font-semibold"><?php echo $index + 1; ?></td>
<td class="py-2.5 px-3">
<div class="font-bold text-slate-800 station-name"><?php echo htmlspecialchars($stationName); ?></div>
</td>
<td class="py-2.5 px-3 text-center">
<select class="tx-level tx-rx-select w-full py-1.5 px-2 rounded-lg border border-slate-200 text-xs font-bold text-slate-700 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all bg-emerald-50 text-emerald-700 border-emerald-200">
<option value="5" selected>5/5 - ดีมาก</option>
<option value="4">4/5 - ดี</option>
<option value="3">3/5 - ปานกลาง</option>
<option value="2">2/5 - อ่อน</option>
<option value="1">1/5 - แย่มาก</option>
<option value="ติดต่อไม่ได้">❌ ติดต่อไม่ได้</option>
</select>
</td>
<td class="py-2.5 px-3 text-center">
<select class="rx-level tx-rx-select w-full py-1.5 px-2 rounded-lg border border-slate-200 text-xs font-bold text-slate-700 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all bg-emerald-50 text-emerald-700 border-emerald-200">
<option value="5" selected>5/5 - ดีมาก</option>
<option value="4">4/5 - ดี</option>
<option value="3">3/5 - ปานกลาง</option>
<option value="2">2/5 - อ่อน</option>
<option value="1">1/5 - แย่มาก</option>
<option value="ติดต่อไม่ได้">❌ ติดต่อไม่ได้</option>
</select>
</td>
<td class="py-2.5 px-3 text-center">
<div class="flex rounded-lg overflow-hidden border border-slate-200 p-0.5 bg-slate-100">
<input type="hidden" class="channel-type" value="T">
<button type="button" class="btn-channel-t flex-1 py-1 text-[10px] font-bold rounded-md transition-all cursor-pointer bg-indigo-600 text-white shadow-sm">T (ช่องตรง)</button>
<button type="button" class="btn-channel-r flex-1 py-1 text-[10px] font-bold rounded-md transition-all cursor-pointer text-slate-600 hover:text-slate-800">R (Repeater)</button>
</div>
</td>
<td class="py-2.5 px-3">
<input type="text" class="note-input w-full px-2.5 py-1.5 border border-slate-200 rounded-lg text-xs font-medium outline-none focus:border-indigo-400 focus:ring-1 focus:ring-indigo-400 transition-all bg-slate-50/50 hover:bg-white focus:bg-white" placeholder="ไม่มีความเห็นพิเศษ...">
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Extra 5 Stations Table -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden" id="extra-stations-container">
<div class="p-4 md:p-5 bg-slate-50 border-b border-slate-100">
<h3 class="text-base font-bold text-slate-800 flex items-center gap-2">
<i data-lucide="plus-circle" class="w-4 h-4 text-emerald-600"></i> ตารางทดสอบวิทยุเพิ่มเติม (ลูกข่ายอื่น ๆ 5 ช่องพิเศษ)
</h3>
<p class="text-xs text-slate-500 mt-0.5">ระบุชื่อลูกข่ายและผลทดสอบวิทยุ (หากมีการกรอกชื่อ ระบบจึงจะนำข้อมูลมาคำนวณจำนวนผลสรุป)</p>
</div>
<div class="p-4 space-y-4">
<div class="grid grid-cols-1 md:grid-cols-5 gap-3 font-bold text-xs text-slate-500 border-b border-slate-100 pb-2 hidden md:grid">
<div class="col-span-2 px-1">ระบุชื่อสถานีลูกข่ายเพิ่มเติม</div>
<div class="text-center">สัญญาณส่ง (TX)</div>
<div class="text-center">สัญญาณรับ (RX)</div>
<div class="px-1">หมายเหตุเพิ่มเติม</div>
</div>
<?php for($i = 0; $i < 5; $i++): ?>
<div class="radio-row extra-row grid grid-cols-1 md:grid-cols-5 gap-3 items-center bg-slate-50/30 p-2.5 md:p-1 rounded-xl border border-slate-100 md:border-none md:bg-transparent" data-id="extra_<?php echo $i; ?>">
<div class="col-span-2 flex items-center gap-2">
<span class="text-xs font-bold text-slate-400 w-6 text-center"><?php echo $i + 17; ?></span>
<input type="text" placeholder="เช่น กู้ชีพตลิ่งงาม (ปล่อยว่างเพื่อไม่บันทึก)" class="station-name-input flex-1 px-3 py-2 border border-slate-200 rounded-lg text-xs font-semibold outline-none focus:border-emerald-400 focus:ring-1 focus:ring-emerald-400 transition-all bg-white">
<input type="hidden" class="channel-type" value="T">
</div>
<div>
<select disabled class="tx-level tx-rx-select w-full py-2 px-2.5 rounded-lg border border-slate-200 text-xs font-bold text-slate-700 outline-none transition-all disabled:opacity-50 disabled:bg-slate-50 bg-emerald-50 text-emerald-700 border-emerald-200">
<option value="5" selected>5/5 - ดีมาก</option>
<option value="4">4/5 - ดี</option>
<option value="3">3/5 - ปานกลาง</option>
<option value="2">2/5 - อ่อน</option>
<option value="1">1/5 - แย่มาก</option>
<option value="ติดต่อไม่ได้">❌ ติดต่อไม่ได้</option>
</select>
</div>
<div>
<select disabled class="rx-level tx-rx-select w-full py-2 px-2.5 rounded-lg border border-slate-200 text-xs font-bold text-slate-700 outline-none transition-all disabled:opacity-50 disabled:bg-slate-50 bg-emerald-50 text-emerald-700 border-emerald-200">
<option value="5" selected>5/5 - ดีมาก</option>
<option value="4">4/5 - ดี</option>
<option value="3">3/5 - ปานกลาง</option>
<option value="2">2/5 - อ่อน</option>
<option value="1">1/5 - แย่มาก</option>
<option value="ติดต่อไม่ได้">❌ ติดต่อไม่ได้</option>
</select>
</div>
<div class="col-span-1">
<input type="text" disabled placeholder="หมายเหตุเพิ่มเติม..." class="note-input w-full px-3 py-2 border border-slate-200 rounded-lg text-xs font-medium outline-none focus:border-emerald-400 focus:ring-1 focus:ring-emerald-400 transition-all bg-white disabled:opacity-50 disabled:bg-slate-50">
</div>
</div>
<?php endfor; ?>
</div>
</div>
<!-- Bottom Announcement Box -->
<div class="bg-gradient-to-br from-indigo-900 to-slate-900 text-white rounded-2xl p-5 md:p-6 shadow-lg border border-indigo-800">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2.5">
<i data-lucide="radio" class="w-5 h-5 text-indigo-300 animate-pulse"></i>
<span class="text-sm font-bold uppercase tracking-wider text-indigo-200">ข้อความประกาศ ว.16 - ปิดการทดสอบ</span>
</div>
<button type="button" class="flex items-center gap-1.5 bg-indigo-800/80 hover:bg-indigo-700 text-xs px-3 py-1.5 rounded-lg border border-indigo-600/50 transition-all cursor-pointer font-medium" onclick="copyEndAnnouncement()">
<i data-lucide="copy" class="w-3.5 h-3.5"></i> คัดลอกบทพูด
</button>
</div>
<div class="bg-slate-950/50 p-4 rounded-xl border border-indigo-800/50 font-mono text-xs md:text-sm leading-relaxed whitespace-pre-line text-slate-100" id="announce-end-text"><?php echo $announceEnd; ?></div>
</div>
<!-- Footer Confirmation Bar -->
<div class="bg-slate-50 rounded-2xl p-4 md:p-5 border border-slate-200/80 flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-3">
<div class="p-2 bg-indigo-100 rounded-xl text-indigo-700 border border-indigo-200/50">
<i data-lucide="check-circle" class="w-5 h-5"></i>
</div>
<div>
<h4 class="text-sm font-bold text-slate-800">ยืนยันและสรุปผลตรวจ ว.16</h4>
<p class="text-xs text-slate-400 mt-0.5 font-medium">เข้าร่วม <span id="summary-tested">16</span> สถานี | ติดต่อไม่ได้ <span id="summary-untested">0</span> สถานี | ผู้ทดสอบ: <?php echo htmlspecialchars($currentUserFullName); ?></p>
</div>
</div>
<button type="button" id="btn-save" class="w-full sm:w-auto flex items-center justify-center gap-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-sm px-6 py-3 rounded-xl shadow-md shadow-indigo-100 transition-all active:scale-95 cursor-pointer">
<i data-lucide="save" class="w-4 h-4"></i> บันทึกและส่งข้อมูลเข้ารายงานเวร
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const updateCalculations = () => {
let testedCount = 0;
let untestedCount = 0;
document.querySelectorAll('.radio-row').forEach(row => {
const isExtra = row.classList.contains('extra-row');
let name = '';
if (isExtra) {
name = row.querySelector('.station-name-input').value.trim();
if (!name) return; // skip empty extra rows
}
const tx = row.querySelector('.tx-level').value;
const rx = row.querySelector('.rx-level').value;
if (tx === 'ติดต่อไม่ได้' && rx === 'ติดต่อไม่ได้') {
untestedCount++;
} else {
testedCount++;
}
});
document.getElementById('summary-tested').innerText = testedCount;
document.getElementById('announce-tested').innerText = testedCount;
document.getElementById('summary-untested').innerText = untestedCount;
};
// Colorize selects and update calculations
document.querySelectorAll('.tx-rx-select').forEach(select => {
select.addEventListener('change', function() {
this.classList.remove('bg-emerald-50', 'text-emerald-700', 'border-emerald-200', 'bg-rose-100', 'text-rose-700', 'border-rose-200');
if (this.value === '5') {
this.classList.add('bg-emerald-50', 'text-emerald-700', 'border-emerald-200');
} else if (this.value === 'ติดต่อไม่ได้') {
this.classList.add('bg-rose-100', 'text-rose-700', 'border-rose-200');
}
// Check row red background
const row = this.closest('tr');
if (row) {
const tx = row.querySelector('.tx-level').value;
const rx = row.querySelector('.rx-level').value;
if (tx === 'ติดต่อไม่ได้' && rx === 'ติดต่อไม่ได้') {
row.classList.add('bg-red-50/20');
} else {
row.classList.remove('bg-red-50/20');
}
}
updateCalculations();
});
});
// T/R buttons
document.querySelectorAll('.btn-channel-t').forEach(btn => {
btn.addEventListener('click', function() {
const hidden = this.parentElement.querySelector('.channel-type');
hidden.value = 'T';
this.classList.add('bg-indigo-600','text-white','shadow-sm');
this.classList.remove('text-slate-600','hover:text-slate-800');
this.nextElementSibling.classList.remove('bg-indigo-600','text-white','shadow-sm');
this.nextElementSibling.classList.add('text-slate-600','hover:text-slate-800');
});
});
document.querySelectorAll('.btn-channel-r').forEach(btn => {
btn.addEventListener('click', function() {
const hidden = this.parentElement.querySelector('.channel-type');
hidden.value = 'R';
this.classList.add('bg-indigo-600','text-white','shadow-sm');
this.classList.remove('text-slate-600','hover:text-slate-800');
this.previousElementSibling.classList.remove('bg-indigo-600','text-white','shadow-sm');
this.previousElementSibling.classList.add('text-slate-600','hover:text-slate-800');
});
});
// Extra stations
document.querySelectorAll('.station-name-input').forEach(input => {
input.addEventListener('input', function() {
const active = this.value.trim() !== '';
const selects = this.parentElement.parentElement.querySelectorAll('select, input[type=text]:not(.station-name-input)');
selects.forEach(el => el.disabled = !active);
updateCalculations();
});
});
window.copyEndAnnouncement = () => {
const text = document.getElementById('announce-end-text').innerText;
navigator.clipboard.writeText(text);
Swal.fire({title: 'สำเร็จ', text: 'คัดลอกข้อความ เรียบร้อยแล้ว', icon: 'success', timer: 1500, showConfirmButton: false});
};
// Save logic
document.getElementById('btn-save').addEventListener('click', async () => {
const btn = document.getElementById('btn-save');
btn.disabled = true;
const originalText = btn.innerHTML;
btn.innerHTML = 'กำลังบันทึก...';
let items = [];
document.querySelectorAll('.radio-row').forEach(row => {
const isExtra = row.classList.contains('extra-row');
let name = '';
if (isExtra) {
name = row.querySelector('.station-name-input').value.trim();
if (!name) return;
} else {
name = row.querySelector('.station-name').innerText.trim();
}
items.push({
station_id: row.dataset.id,
station_name: name,
tx_level: row.querySelector('.tx-level').value,
rx_level: row.querySelector('.rx-level').value,
channel_type: row.querySelector('.channel-type').value,
note: row.querySelector('.note-input').value.trim()
});
});
const payload = {
date: new Date().toISOString().split('T')[0],
time: '<?php echo $currentTime; ?>',
shift: '<?php echo $shift; ?>',
tested_count: document.getElementById('summary-tested').innerText,
untested_count: document.getElementById('summary-untested').innerText,
items: items
};
try {
const response = await fetch('api/save_radio_test.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
if (data.success) {
Swal.fire({
title: 'สำเร็จ',
text: data.message,
icon: 'success',
confirmButtonColor: '#4f46e5'
}).then(() => {
window.scrollTo({top: 0, behavior: 'smooth'});
});
} else {
Swal.fire({
title: 'ข้อผิดพลาด',
text: data.message,
icon: 'error',
confirmButtonColor: '#ef4444'
});
}
} catch (error) {
console.error(error);
Swal.fire({
title: 'ข้อผิดพลาด',
text: 'เกิดข้อผิดพลาดในการเชื่อมต่อ',
icon: 'error',
confirmButtonColor: '#ef4444'
});
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
}
});
});
</script>
<?php require_once 'includes/footer.php'; ?>