| Server IP : 103.233.193.20 / Your IP : 216.73.216.169 Web Server : Apache/2 System : Linux host1.itclever.com 4.18.0-553.16.1.el8_10.x86_64 #1 SMP Thu Aug 8 17:47:08 UTC 2024 x86_64 User : oriscomadm ( 1120) PHP Version : 5.6.40 Disable Function : exec,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname MySQL : ON | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /home/oriscomadm/domains/oriscom.com/private_html/taxi_estimate/ |
Upload File : |
// ========================================================================================================
// SECTION 1: CONFIGURATION & STATE ส่วนกำหนดค่าคงที่ (Config) และตัวแปรสถานะหลักของระบบ
// ========================================================================================================
const CONFIG = {
API_KEY: '4fc6a833488e90b3df56acc1388c198b', // API Key ของ Longdo Map
SESSION_ID: 'session_[timestamp]_[random]', // ID เซสชันไม่ซ้ำกัน
ROUTE_COLORS: { // สีของเส้นทางบนแผนที่
initial: { primary: '#A0AEC0', fallback: '#CBD5E0' }, // สีเทา
main: { primary: '#1E90FF', fallback: '#63B3ED' }, // สีน้ำเงิน
fastest: { primary: '#6A5ACD', fallback: '#9370DB' } // สีม่วง
}
}
const STATE = {
currentLang: 'th', // ภาษาที่ใช้ (th/en)
selectedOrigin: null, // จุดเริ่มต้นที่เลือก
selectedDestination: null, // จุดหมายปลายทางที่เลือก
selectedRouteType: null, // ประเภทเส้นทางที่เลือก (main/fastest)
routeData: {}, // ข้อมูลเส้นทางที่คำนวณแล้ว
currentMapMode: 'origin', // โหมดการคลิกแผนที่ (origin/destination)
map: null, // ออบเจ็กต์แผนที่
markers: { origin: null, destination: null }, // หมุดบนแผนที่
searchTimeout: null, // ตัวจับเวลาสำหรับการค้นหา
isAutoLocating: false // กำลังหาตำแหน่งอัตโนมัติหรือไม่
}
const ROUTE_CALC_STATE = {
calculating: false, // กำลังคำนวณอยู่หรือไม่
currentStep: 0, // ขั้นตอนปัจจุบัน (0-3)
results: {}, // ผลลัพธ์การคำนวณแต่ละขั้นตอน
steps: [ // 4 ขั้นตอนการคำนวณ
{ name: 'AllDrive_Distance', routeType: 'AllDrive', mode: longdo.RouteMode.Cost },
{ name: 'AllDrive_Traffic', routeType: 'AllDrive', mode: longdo.RouteMode.Traffic },
{ name: 'Road_Distance', routeType: 'Road', mode: longdo.RouteMode.Cost },
{ name: 'Road_Traffic', routeType: 'Road', mode: longdo.RouteMode.Traffic }
]
}
// ========================================================================================================
// SECTION 2: MARKER & ICON HELPERS ส่วนสร้างไอคอนหมุด (Marker) สำหรับแผนที่
// ========================================================================================================
const createMarkerIcon = (color, label) => {
// สร้าง SVG หมุดสี่เหลี่ยมหยดน้ำ พร้อมตัวอักษร S (Start) หรือ E (End)
const svg = `
<svg width="40" height="50" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2C11.7 2 5 8.7 5 17c0 8.3 15 31 15 31s15-22.7 15-31c0-8.3-6.7-15-15-15z"
fill="${color}" stroke="#fff" stroke-width="2"/>
<circle cx="20" cy="17" r="11" fill="white"/>
<text x="20" y="22" text-anchor="middle" font-size="16" font-weight="bold" fill="${color}">${label}</text>
</svg>
`.trim();
return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`;
}
const MARKERS = {
green: createMarkerIcon('#22c55e', 'S'), // หมุดสีเขียว (จุดเริ่มต้น)
red: createMarkerIcon('#ef4444', 'E') // หมุดสีแดง (จุดปลายทาง)
}
// ========================================================================================================
// SECTION 3: GPS & LOCATION DETECTION จัดการการขอสิทธิ์ GPS และการหาตำแหน่งปัจจุบัน
// ========================================================================================================
async function checkAndRequestGPSPermission() {
const t = TRANSLATIONS[STATE.currentLang];
if (!navigator.geolocation) {
console.error('⌠Geolocation is not supported by this browser');
return false;
}
if (navigator.permissions && navigator.permissions.query) {
try {
const permissionStatus = await navigator.permissions.query({ name: 'geolocation' });
console.log('📍 GPS Permission Status:', permissionStatus.state);
if (permissionStatus.state === 'denied') {
showGPSAlert(t.gps_denied_message);
return false;
} else if (permissionStatus.state === 'prompt') {
showGPSAlert(t.gps_permission_message, true);
}
permissionStatus.addEventListener('change', function () {
console.log('🔄 GPS permission changed to:', this.state);
if (this.state === 'granted' && !STATE.selectedOrigin) {
autoDetectCurrentLocation();
}
});
} catch (error) {
console.log('⚠️ Permission API not fully supported:', error);
}
} else {
console.log('⚠️ Permissions API not supported, requesting location directly');
showGPSAlert(t.gps_permission_message, true);
}
return true;
}
function showGPSAlert(message, isInfo = false) {
const alertDiv = document.createElement('div');
alertDiv.style.cssText = `
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
background: ${isInfo ? '#0d6efd' : '#dc3545'}; color: white;
padding: 15px 25px; border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 10000;
max-width: 90%; text-align: center; font-size: 14px; line-height: 1.6;
animation: slideDown 0.3s ease-out;
`;
alertDiv.innerHTML = `
<div style="display: flex; align-items: center; gap: 10px;">
<i class="bi bi-${isInfo ? 'info-circle' : 'exclamation-triangle'}" style="font-size: 24px;"></i>
<div style="white-space: pre-line; text-align: left;">${message}</div>
</div>
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
alertDiv.style.animation = 'slideUp 0.2s ease-in';
setTimeout(() => alertDiv.remove(), 200);
}, 2000);
if (!document.getElementById('gpsAlertStyle')) {
const style = document.createElement('style');
style.id = 'gpsAlertStyle';
style.textContent = `
@keyframes slideDown {
from { transform: translate(-50%, -100%); opacity: 0; }
to { transform: translate(-50%, 0); opacity: 1; }
}
@keyframes slideUp {
from { transform: translate(-50%, 0); opacity: 1; }
to { transform: translate(-50%, -100%); opacity: 0; }
}
`;
document.head.appendChild(style);
}
}
function autoDetectCurrentLocation() {
const t = TRANSLATIONS[STATE.currentLang];
if (STATE.isAutoLocating) {
console.log('⚠️ Already detecting location...');
return;
}
STATE.isAutoLocating = true;
const originInput = document.getElementById('origin');
originInput.value = t.auto_detecting_location;
originInput.style.color = '#0d6efd';
originInput.style.fontWeight = '300';
console.log('📍 Auto-detecting current location...');
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('✓ Location detected:', position.coords);
if (STATE.map) {
STATE.map.location({
lon: position.coords.longitude,
lat: position.coords.latitude
}, true);
STATE.map.zoom(15);
}
reverseGeocode(
position.coords.latitude,
position.coords.longitude,
'origin',
true
);
STATE.isAutoLocating = false;
showSuccessMessage(t.location_found);
},
(error) => {
console.error('⌠GPS Error:', error);
STATE.isAutoLocating = false;
originInput.value = '';
originInput.style.color = '';
originInput.style.fontWeight = '';
let errorMessage = t.alert_gps_error;
switch (error.code) {
case error.PERMISSION_DENIED:
errorMessage = t.gps_denied_message;
break;
case error.POSITION_UNAVAILABLE:
errorMessage = "ไม่สามารถหาตำแหน่งได้ กรุณาตรวจสอบว่าเปิด GPS แล้ว";
break;
case error.TIMEOUT:
errorMessage = "หาตำแหน่งล่าช้าเกินไป กรุณาลองใหม่อีกครั้ง";
break;
}
showGPSAlert(errorMessage);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
}
function showSuccessMessage(message) {
const successDiv = document.createElement('div');
successDiv.style.cssText = `
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
background: #198754; color: white; padding: 12px 20px;
border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 10000; font-size: 16px; animation: slideDown 0.3s ease-out;
`;
successDiv.textContent = message;
document.body.appendChild(successDiv);
setTimeout(() => {
successDiv.style.animation = 'slideUp 0.3s ease-out';
setTimeout(() => successDiv.remove(), 300);
}, 3000);
}
// ========================================================================================================
// SECTION 4: LANGUAGE MANAGEMENT จัดการระบบหลายภาษา (ไทย / อังกฤษ)
// ========================================================================================================
function setLanguage(lang) {
STATE.currentLang = lang;
localStorage.setItem('language', lang);
updateLanguage();
updateLanguageSwitcher();
reloadMap();
}
function updateLanguageSwitcher() {
document.querySelectorAll('.lang-option').forEach(el => el.classList.remove('active'));
document.getElementById('lang' + STATE.currentLang.toUpperCase()).classList.add('active');
}
function updateLanguage() {
const t = TRANSLATIONS[STATE.currentLang];
document.getElementById('pageTitle').textContent = t.page_title;
document.documentElement.lang = STATE.currentLang;
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (t[key]) el.textContent = t[key];
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
if (t[key]) el.placeholder = t[key];
});
updateLocationInputs();
if (STATE.routeData.main && STATE.routeData.fastest) {
displayRoutes(STATE.routeData.main, STATE.routeData.fastest);
displayDebugInfo();
}
if (STATE.map) STATE.map.language(STATE.currentLang);
console.log(`🌐 Language switched to: ${STATE.currentLang}`);
}
function updateLocationInputs() {
const t = TRANSLATIONS[STATE.currentLang];
if (STATE.selectedOrigin) {
if (STATE.selectedOrigin.isCurrentLocation) {
document.getElementById('origin').value = t.current_location;
} else {
reverseGeocode(STATE.selectedOrigin.lat, STATE.selectedOrigin.lon, 'origin', false);
}
}
if (STATE.selectedDestination) {
if (STATE.selectedDestination.isCurrentLocation) {
document.getElementById('destination').value = t.current_location;
} else {
reverseGeocode(STATE.selectedDestination.lat, STATE.selectedDestination.lon, 'destination', false);
}
}
}
// ========================================================================================================
// SECTION 5: MAP MANAGEMENT จัดการการทำงานของแผนที่ Longdo
// ========================================================================================================
function initMap() {
STATE.map = new longdo.Map({
placeholder: document.getElementById('map'),
language: STATE.currentLang,
zoom: 12
});
STATE.map.Event.bind('click', function () {
const loc = STATE.map.location(longdo.LocationMode.Pointer);
handleMapClick(loc.lat, loc.lon);
});
STATE.map.location({ lon: 100.5, lat: 13.75 }, true);
initRouteEventBinding();
console.log('🗺️ Map initialized');
}
function reloadMap() {
if (!STATE.map) return;
const oldOrigin = STATE.selectedOrigin;
const oldDestination = STATE.selectedDestination;
document.getElementById('map').innerHTML = '';
initMap();
setTimeout(() => {
if (oldOrigin) {
updateLocation('origin', oldOrigin.name, oldOrigin.actualName, oldOrigin.lat, oldOrigin.lon, oldOrigin.isCurrentLocation);
}
if (oldDestination) {
updateLocation('destination', oldDestination.name, oldDestination.actualName, oldDestination.lat, oldDestination.lon, oldDestination.isCurrentLocation);
}
if (STATE.routeData.main && STATE.routeData.fastest) {
displayRoutes(STATE.routeData.main, STATE.routeData.fastest);
}
}, 300);
console.log('🔄 Map reloaded for language change');
}
function setMapMode(mode) {
STATE.currentMapMode = mode;
console.log(`🎯 Map mode set to: ${mode}`);
}
function handleMapClick(lat, lon) {
console.log(`📍 Map clicked: ${lat}, ${lon}`);
reverseGeocode(lat, lon, STATE.currentMapMode, false);
}
// ========================================================================================================
// SECTION 6: GEOCODING
// ส่วนแปลงพิกัดภูมิศาสตร์ (latitude, longitude)
// ========================================================================================================
// --------------------------------------------------------------------------------------------------------
// reverseGeocode()
// ฟังก์ชันแปลงพิกัด (lat, lon) → ชื่อสถานที่
//
// พารามิเตอร์:
// lat = ค่าละติจูด
// lon = ค่าลองจิจูด
// type = ประเภทตำแหน่ง ('origin' หรือ 'destination')
// isCurrentLocation = เป็นตำแหน่งปัจจุบันจาก GPS หรือไม่ (true / false)
//
// การทำงาน:
// 1. เรียก Longdo Address API เพื่อขอข้อมูลที่อยู่จากพิกัด
// 2. นำข้อมูลที่ได้มาเรียบเรียงเป็นชื่อสถานที่
// 3. อัปเดตค่า input, marker และ STATE ของระบบ
// 4. หากเกิดข้อผิดพลาด จะใช้พิกัดแทนชื่อสถานที่
// --------------------------------------------------------------------------------------------------------
async function reverseGeocode(lat, lon, type, isCurrentLocation = false) {
// ดึงข้อความตามภาษาปัจจุบัน (ไทย / อังกฤษ)
const t = TRANSLATIONS[STATE.currentLang];
try {
// 🔥 เรียก API สองครั้ง: ครั้งแรกภาษาปัจจุบัน (สำหรับแสดงผล), ครั้งสองภาษาไทย (สำหรับบันทึก)
// 1️⃣ เรียก API ด้วยภาษาปัจจุบัน (สำหรับแสดงใน UI)
const responseCurrentLang = await fetch(
`https://api.longdo.com/map/services/address?lon=${lon}&lat=${lat}&locale=${STATE.currentLang}&key=${CONFIG.API_KEY}`
);
const dataCurrentLang = await responseCurrentLang.json();
const actualName = formatLocationName(dataCurrentLang, STATE.currentLang) || `${lat.toFixed(6)}, ${lon.toFixed(6)}`;
// 2️⃣ เรียก API ด้วยภาษาไทย (สำหรับบันทึกลงฐานข้อมูล)
let actualNameThai = actualName; // ค่าเริ่มต้น
if (STATE.currentLang !== 'th') {
// ถ้าภาษาปัจจุบันไม่ใช่ภาษาไทย ให้เรียก API อีกครั้งด้วยภาษาไทย
try {
const responseThai = await fetch(
`https://api.longdo.com/map/services/address?lon=${lon}&lat=${lat}&locale=th&key=${CONFIG.API_KEY}`
);
const dataThai = await responseThai.json();
actualNameThai = formatLocationName(dataThai, 'th') || actualName;
console.log(`🇹🇭 Thai name for database: ${actualNameThai}`);
} catch (error) {
console.error('⚠️ Failed to get Thai name, using current language:', error);
}
}
// ชื่อที่แสดงใน UI: ถ้าเป็นตำแหน่งปัจจุบัน แสดง "(ตำแหน่งปัจจุบัน)" ถ้าไม่ใช่ แสดงชื่อจริง
const displayName = isCurrentLocation ? t.current_location : actualName;
// อัปเดตตำแหน่ง (ต้นทาง / ปลายทาง)
updateLocation(
type,
displayName, // ชื่อที่แสดงใน input
actualName, // ชื่อจริงตามภาษาปัจจุบัน
actualNameThai, // ชื่อภาษาไทย (สำหรับบันทึกฐานข้อมูล)
lat,
lon,
isCurrentLocation
);
console.log(`✓ Reverse geocode success: ${actualName} (Thai: ${actualNameThai})`);
} catch (error) {
// กรณีเรียก API ไม่สำเร็จ หรือเกิดข้อผิดพลาดอื่น ๆ
console.error('⌠Reverse Geocode Error:', error);
// ใช้พิกัด (lat, lon) เป็นชื่อสถานที่แทน
const coordName = `${lat.toFixed(6)}, ${lon.toFixed(6)}`;
const displayName = isCurrentLocation ? t.current_location : coordName;
updateLocation(
type,
displayName,
coordName,
coordName, // ใช้พิกัดทั้งสองภาษา
lat,
lon,
isCurrentLocation
);
}
}
// --------------------------------------------------------------------------------------------------------
// formatLocationName() ฟังก์ชันจัดรูปแบบชื่อสถานที่จากข้อมูล Address API
// พารามิเตอร์: data = ข้อมูลที่อยู่ที่ได้จาก Longdo API
// การทำงาน:
// 1. ตรวจสอบภาษาปัจจุบัน (ไทย / อังกฤษ)
// 2. นำข้อมูลที่อยู่แต่ละส่วนมาเรียงลำดับให้เหมาะสม
// 3. รวมเป็นข้อความเดียวเพื่อแสดงผลให้ผู้ใช้
// 4. หากไม่มีข้อมูลเพียงพอ จะคืนค่า null
// --------------------------------------------------------------------------------------------------------
function formatLocationName(data, lang = null) {
const parts = []; // เก็บส่วนประกอบของชื่อสถานที่
const isTH = (lang || STATE.currentLang) === "th"; // ตรวจสอบว่าเป็นภาษาไทยหรือไม่
// เพิ่มเลขที่บ้าน (ถ้ามี)
if (data.house_no) {
parts.push(isTH ? `เลขที่ ${data.house_no}` : `No. ${data.house_no}`);
}
// รายการข้อมูลที่อยู่ตามลำดับความสำคัญ
[
'moo', // หมู่
'place', // สถานที่
'building', // อาคาร
'condominium', // คอนโด
'village', // หมู่บ้าน
'village_official', // หมู่บ้าน (ทางการ)
'sublane', // ซอยย่อย
'alley', // ซอย
'road', // ถนน
'subdistrict', // แขวง / ตำบล
'district', // เขต / อำเภอ
'province' // จังหวัด
, 'point'
].forEach(key => {
// เพิ่มเฉพาะข้อมูลที่มีค่า
if (data[key]) parts.push(data[key]);
});
// รวมชื่อสถานที่ทั้งหมดเป็นสตริงเดียว
// หากไม่มีข้อมูลเลย ให้คืนค่า null
return parts.length > 0 ? parts.join(' ') : null;
}
// ========================================================================================================
// SECTION 7: LOCATION UPDATE & MARKERS
// จัดการตำแหน่งต้นทาง / ปลายทาง, input และ marker บนแผนที่
// ========================================================================================================
function updateLocation(type, displayName, actualName, actualNameThai, lat, lon, isCurrentLocation = false) {
// รวมข้อมูลตำแหน่งเป็น object เดียว
const locationData = {
name: displayName, // ชื่อที่แสดงใน UI
actualName: actualName, // ชื่อจริงตามภาษาปัจจุบัน
actualNameThai: actualNameThai, // 🔥 ชื่อภาษาไทย (สำหรับบันทึกฐานข้อมูล)
lat,
lon,
isCurrentLocation
};
// ใช้ type เป็น id ของ input (origin / destination)
const inputId = type;
// กำหนดสี marker ตามประเภทตำแหน่ง
const markerColor = type === 'origin' ? MARKERS.green : MARKERS.red;
// บันทึกตำแหน่งต้นทางลง STATE
if (type === 'origin') {
STATE.selectedOrigin = locationData;
}
// บันทึกตำแหน่งปลายทางลง STATE
else {
STATE.selectedDestination = locationData;
}
// ดึง input ตาม id
const input = document.getElementById(inputId);
// แสดงชื่อสถานที่ใน input (แสดงชื่อ UI)
input.value = displayName;
// ถ้าเป็นตำแหน่งปัจจุบัน → เปลี่ยนสีตัวอักษร
input.style.color = isCurrentLocation ? '#198754' : '';
// ถ้าเป็นตำแหน่งปัจจุบัน → ทำตัวหนา
input.style.fontWeight = isCurrentLocation ? '600' : '';
// แสดงปุ่มล้างข้อมูล (X)
toggleClearButton(inputId, true);
// ถ้ามี marker เดิม → ลบออกก่อน
if (STATE.markers[type]) {
STATE.map.Overlays.remove(STATE.markers[type]);
}
// สร้าง marker ใหม่ตามพิกัด
STATE.markers[type] = new longdo.Marker(
{ lat, lon }, // พิกัด marker
{
icon: {
url: markerColor, // ไอคอนตามสี
offset: { x: 20, y: 50 } // ปรับตำแหน่งไอคอน
},
title: actualName // ชื่อจริงแสดงบน marker
}
);
// เพิ่ม marker ลงบนแผนที่
STATE.map.Overlays.add(STATE.markers[type]);
// เลื่อนแผนที่ไปยังตำแหน่งที่เลือก
STATE.map.location({ lat, lon }, true);
// แสดง log เพื่อ debug
console.log(`📍 Location updated: ${type} = ${displayName} (${actualName})`);
}
function clearInput(type) {
// ดึง input ที่ต้องการล้าง
const input = document.getElementById(type);
// ล้างค่าข้อความใน input
input.value = '';
// รีเซ็ตสีตัวอักษร
input.style.color = '';
// รีเซ็ตความหนาตัวอักษร
input.style.fontWeight = '';
// ล้างข้อมูลต้นทางใน STATE
if (type === 'origin') {
STATE.selectedOrigin = null;
}
// ล้างข้อมูลปลายทางใน STATE
else {
STATE.selectedDestination = null;
}
// ซ่อนปุ่มล้างข้อมูล
toggleClearButton(type, false);
// ถ้ามี marker → ลบออกจากแผนที่
if (STATE.markers[type]) {
STATE.map.Overlays.remove(STATE.markers[type]);
STATE.markers[type] = null;
}
// ถ้าข้อมูลต้นทางหรือปลายทางไม่ครบ
if (!STATE.selectedOrigin || !STATE.selectedDestination) {
// ลบเส้นทางออกจากแผนที่
if (STATE.map?.Route) STATE.map.Route.clear();
// ซ่อนส่วนแสดงเส้นทาง
const section = document.getElementById('routesSection');
if (section) section.style.display = 'none';
// ซ่อนปุ่มยืนยันเส้นทาง
const confirm = document.getElementById('confirmBtn');
if (confirm) confirm.style.display = 'none';
// ซ่อน debug panel ตัวที่ 1
const debug1 = document.getElementById('debug1');
if (debug1) debug1.classList.remove('show');
// ซ่อน debug panel ตัวที่ 2
const debug2 = document.getElementById('debug2');
if (debug2) debug2.classList.remove('show');
// รีเซ็ตข้อมูลเส้นทางหลัก
STATE.routeData = {};
// รีเซ็ตข้อมูลเส้นทาง debug
STATE.routeDataDebug = {};
}
// แสดง log ว่าล้างข้อมูลแล้ว
console.log(`🗑️ Cleared: ${type}`);
}
function toggleClearButton(inputId, show) {
// สร้าง id ปุ่มล้างข้อมูลจาก inputId
const btnId =
'clear' +
inputId.charAt(0).toUpperCase() + // ตัวอักษรแรกเป็นพิมพ์ใหญ่
inputId.slice(1); // ต่อด้วยตัวที่เหลือ
// ดึงปุ่มล้างข้อมูล
const btn = document.getElementById(btnId);
// แสดงหรือซ่อนปุ่มด้วย class 'show'
if (btn) {
btn.classList.toggle('show', show);
}
}
// ========================================================================================================
// SECTION 8: SEARCH & SUGGESTIONS
// จัดการค้นหาสถานที่ และแสดงรายการแนะนำ
// ========================================================================================================
async function searchPlace(keyword, element, isOrigin) {
// ถ้าไม่มีคำค้น หรือเป็นค่าว่าง → ซ่อนกล่องแนะนำ
if (!keyword || keyword.trim().length < 1) {
element.style.display = 'none';
return;
}
try {
// สร้าง URL สำหรับค้นหาสถานที่จาก Longdo Search API
const url =
`https://search.longdo.com/mapsearch/json/search?` +
`keyword=${encodeURIComponent(keyword)}` + // เข้ารหัสคำค้น
`&limit=5` + // จำกัดผลลัพธ์ 5 รายการ
`&locale=${STATE.currentLang}` + // ภาษา
`&key=${CONFIG.API_KEY}`; // API Key
// เรียก API
const response = await fetch(url);
// แปลงผลลัพธ์เป็น JSON
const data = await response.json();
// แสดงรายการแนะนำ (ถ้าไม่มีข้อมูล ส่ง array ว่าง)
displaySuggestions(data?.data || [], element, isOrigin);
} catch (error) {
// แสดง error กรณีค้นหาล้มเหลว
console.error('⌠Search Error:', error);
// แสดงกล่องแนะนำแบบว่าง
displaySuggestions([], element, isOrigin);
}
}
function displaySuggestions(places, element, isOrigin) {
// ดึงข้อความตามภาษาปัจจุบัน
const t = TRANSLATIONS[STATE.currentLang];
// ล้างรายการแนะนำเดิม
element.innerHTML = '';
// สร้างรายการ "ใช้ตำแหน่งปัจจุบัน"
// แสดง "ใช้ตำแหน่งปัจจุบัน" เฉพาะช่องต้นทาง (Origin)
if (isOrigin) {
const currentItem = document.createElement('div');
currentItem.className = 'suggestion-item current-location-item';
currentItem.innerHTML = `
<i class="bi bi-crosshair text-success me-2"></i>
<strong>${t.use_current_location}</strong>
`;
currentItem.onclick = () => {
useCurrentLocation(true); // ชัดเจนว่าเป็น origin
element.style.display = 'none';
};
element.appendChild(currentItem);
}
// ถ้ามีผลลัพธ์จากการค้นหา
if (places.length > 0) {
// สร้างเส้นแบ่งหัวข้อ "ผลการค้นหา"
const separator = document.createElement('div');
separator.className = 'suggestion-separator';
separator.textContent = t.search_results;
element.appendChild(separator);
// เรียงชื่อสถานที่ตามตัวอักษร
places.sort((a, b) =>
(a.name || "").localeCompare(b.name || "", "th")
);
// วนลูปแสดงแต่ละสถานที่
places.forEach(place => {
// สร้าง item แนะนำ
const item = document.createElement('div');
item.className = 'suggestion-item';
// HTML ของรายการสถานที่
item.innerHTML = `
<i class="bi bi-geo-alt text-secondary me-2"></i>
<div>
<div><strong>${place.name}</strong></div>
${place.address
? `<div class="text-muted small">${place.address}</div>`
: ''
}
</div>
`;
// เมื่อคลิก → แปลงพิกัดเป็นชื่อสถานที่
item.onclick = async () => {
await reverseGeocode(
place.lat, // ละติจูด
place.lon, // ลองจิจูด
isOrigin ? 'origin' : 'destination', // ประเภทตำแหน่ง
false // ไม่ใช่ GPS
);
element.style.display = 'none';
};
// เพิ่ม item ลงในกล่องแนะนำ
element.appendChild(item);
});
}
// แสดงกล่องแนะนำ
element.style.display = 'block';
}
function useCurrentLocation(isOrigin) {
// ดึงข้อความตามภาษาปัจจุบัน
const t = TRANSLATIONS[STATE.currentLang];
// ถ้าเบราว์เซอร์ไม่รองรับ GPS
if (!navigator.geolocation) {
alert(t.alert_gps_error);
return;
}
// เลือก input ตามประเภท (ต้นทาง / ปลายทาง)
const inputId = isOrigin ? 'origin' : 'destination';
const inputEl = document.getElementById(inputId);
// แสดงข้อความกำลังดึงตำแหน่ง
inputEl.value = t.getting_location;
// ขอพิกัดจาก GPS
navigator.geolocation.getCurrentPosition(
// กรณีได้พิกัดสำเร็จ
pos => {
const lat = pos.coords.latitude; // ละติจูด
const lon = pos.coords.longitude; // ลองจิจูด
// เลื่อนแผนที่ไปยังตำแหน่งปัจจุบัน
STATE.map.location({ lon, lat }, true);
// ซูมเข้า
STATE.map.zoom(15);
// แปลงพิกัดเป็นชื่อสถานที่
reverseGeocode(lat, lon, inputId, true);
},
// กรณีเกิดข้อผิดพลาด
error => {
console.error('⌠GPS Error:', error);
alert(t.alert_gps_error);
inputEl.value = '';
},
// ตั้งค่าการดึง GPS
{
enableHighAccuracy: true, // ความแม่นยำสูง
timeout: 10000, // หมดเวลา 10 วินาที
maximumAge: 0 // ไม่ใช้ตำแหน่งเก่า
}
);
}
// ========================================================================================================
// SECTION 9: ROUTE CALCULATION
// ========================================================================================================
function initRouteEventBinding() {
STATE.map.Event.bind('guideComplete', function () {
if (!ROUTE_CALC_STATE.calculating) return;
const step = ROUTE_CALC_STATE.steps[ROUTE_CALC_STATE.currentStep];
try {
const distance = STATE.map.Route.distance();
const distanceText = STATE.map.Route.distance(true);
const interval = STATE.map.Route.interval();
const intervalText = STATE.map.Route.interval(true);
console.log(`✓ ${step.label}: ${distanceText}, ${intervalText}`);
ROUTE_CALC_STATE.results[step.name] = {
distance: distance,
interval: interval,
distanceText: distanceText,
intervalText: intervalText
};
ROUTE_CALC_STATE.currentStep++;
if (ROUTE_CALC_STATE.currentStep < ROUTE_CALC_STATE.steps.length) {
calculateNextStep();
} else {
finishRouteCalculation();
}
} catch (error) {
console.error('⌠Route calculation error:', error);
ROUTE_CALC_STATE.calculating = false;
document.getElementById('loadingSpinner').classList.remove('active');
alert('เกิดข้อผิดพลาดในการคำนวณเส้นทาง');
}
});
console.log('✓ Route event binding initialized');
}
function searchRoute() {
const t = TRANSLATIONS[STATE.currentLang];
if (!STATE.selectedOrigin || !STATE.selectedDestination) {
alert(t.alert_select_locations);
return;
}
ROUTE_CALC_STATE.calculating = true;
ROUTE_CALC_STATE.currentStep = 0;
ROUTE_CALC_STATE.results = {};
document.getElementById('loadingSpinner').classList.add('active');
document.getElementById('routesSection').style.display = 'none';
document.getElementById('confirmBtn').style.display = 'none';
console.log('🔍 Starting route calculation...');
STATE.map.Route.clear();
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedOrigin.lon,
lat: STATE.selectedOrigin.lat
}));
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedDestination.lon,
lat: STATE.selectedDestination.lat
}));
calculateNextStep();
}
function calculateNextStep() {
const step = ROUTE_CALC_STATE.steps[ROUTE_CALC_STATE.currentStep];
console.log(`🔍 Step ${ROUTE_CALC_STATE.currentStep + 1}/4: ${step.label}`);
if (step.routeType === 'AllDrive') {
STATE.map.Route.enableRoute(longdo.RouteType.Tollway, true);
} else {
STATE.map.Route.enableRoute(longdo.RouteType.AllDrive, false);
STATE.map.Route.enableRoute(longdo.RouteType.Road, true);
}
STATE.map.Route.mode(step.mode);
STATE.map.Route.search();
}
function finishRouteCalculation() {
console.log('✓ All routes calculated!');
ROUTE_CALC_STATE.calculating = false;
STATE.routeData = {
main: {
data: [{
distance: ROUTE_CALC_STATE.results.AllDrive_Distance.distance,
interval: ROUTE_CALC_STATE.results.AllDrive_Distance.interval,
distanceText: ROUTE_CALC_STATE.results.AllDrive_Distance.distanceText,
intervalText: ROUTE_CALC_STATE.results.AllDrive_Distance.intervalText
}]
},
fastest: {
data: [{
distance: ROUTE_CALC_STATE.results.Road_Distance.distance,
interval: ROUTE_CALC_STATE.results.Road_Distance.interval,
distanceText: ROUTE_CALC_STATE.results.Road_Distance.distanceText,
intervalText: ROUTE_CALC_STATE.results.Road_Distance.intervalText
}]
}
};
STATE.routeDataDebug = {
main: {
Distance: {
data: [{
distance: ROUTE_CALC_STATE.results.AllDrive_Distance.distance,
interval: ROUTE_CALC_STATE.results.AllDrive_Distance.interval
}]
},
Traffic: {
data: [{
distance: ROUTE_CALC_STATE.results.AllDrive_Traffic.distance,
interval: ROUTE_CALC_STATE.results.AllDrive_Traffic.interval
}]
}
},
fastest: {
Distance: {
data: [{
distance: ROUTE_CALC_STATE.results.Road_Distance.distance,
interval: ROUTE_CALC_STATE.results.Road_Distance.interval
}]
},
Traffic: {
data: [{
distance: ROUTE_CALC_STATE.results.Road_Traffic.distance,
interval: ROUTE_CALC_STATE.results.Road_Traffic.interval
}]
}
}
};
displayRoutes(STATE.routeData.main, STATE.routeData.fastest);
displayDebugInfo();
displayRouteOnMap();
sendDataToTestPage();
document.getElementById('loadingSpinner').classList.remove('active');
document.getElementById('routesSection').style.display = 'block';
document.getElementById('debug1').classList.add('show');
document.getElementById('debug2').classList.add('show');
document.getElementById('routesSection').scrollIntoView({
behavior: 'smooth',
block: 'start'
});
console.log('✓ Routes displayed successfully');
}
function setRouteColor(routeType = 'initial') {
const colors = CONFIG.ROUTE_COLORS[routeType] || CONFIG.ROUTE_COLORS.initial;
try {
STATE.map.call('Route.line', 'road', {
lineColor: colors.primary,
lineWidth: 3,
borderColor: '#000000',
borderWidth: 1
});
} catch (e) {
try {
STATE.map.Route.line('road', {
lineColor: colors.fallback,
lineWidth: 3
});
} catch (err2) {
STATE.map.Route.option({
lineColor: colors.fallback,
lineWidth: 3
});
}
}
}
function displayRouteOnMap() {
STATE.map.Route.clear();
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedOrigin.lon,
lat: STATE.selectedOrigin.lat
}, {
icon: {
url: MARKERS.green,
offset: { x: 20, y: 50 }
}
}));
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedDestination.lon,
lat: STATE.selectedDestination.lat
}, {
icon: {
url: MARKERS.red,
offset: { x: 20, y: 50 }
}
}));
setRouteColor();
STATE.map.Route.mode(longdo.RouteMode.Cost);
STATE.map.Route.enableRestrict(longdo.RouteRestrict.Bike, false);
STATE.map.Route.search();
console.log('🗺️ Initial route displayed on map');
}
// ========================================================================================================
// SECTION 10: ROUTE SELECTION
// ========================================================================================================
function selectRoute(type) {
STATE.selectedRouteType = type;
document.querySelectorAll('.route-card').forEach(c => c.classList.remove('selected'));
document.getElementById(type === 'main' ? 'route1' : 'route2').classList.add('selected');
//document.getElementById('confirmBtn').style.display = 'block';
STATE.map.Route.clear();
if (type === 'main') {
STATE.map.Route.enableRoute(longdo.RouteType.Tollway, true);
} else {
STATE.map.Route.enableRoute(longdo.RouteType.AllDrive, false);
STATE.map.Route.enableRoute(longdo.RouteType.Road, true);
}
STATE.map.Route.mode(longdo.RouteMode.Cost);
STATE.map.Route.enableRestrict(longdo.RouteRestrict.Bike, false);
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedOrigin.lon,
lat: STATE.selectedOrigin.lat
}, {
icon: { url: MARKERS.green, offset: { x: 20, y: 50 } }
}));
STATE.map.Route.add(new longdo.Marker({
lon: STATE.selectedDestination.lon,
lat: STATE.selectedDestination.lat
}, {
icon: { url: MARKERS.red, offset: { x: 20, y: 50 } }
}));
setRouteColor(type);
STATE.map.Route.label(false);
STATE.map.Route.search();
setTimeout(() => fitMapToRoute(), 800);
console.log(`✓ Route selected: ${type}`);
}
function fitMapToRoute() {
if (!STATE.selectedOrigin || !STATE.selectedDestination) return;
try {
const bounds = {
minLon: Math.min(STATE.selectedOrigin.lon, STATE.selectedDestination.lon),
minLat: Math.min(STATE.selectedOrigin.lat, STATE.selectedDestination.lat),
maxLon: Math.max(STATE.selectedOrigin.lon, STATE.selectedDestination.lon),
maxLat: Math.max(STATE.selectedOrigin.lat, STATE.selectedDestination.lat)
};
const padding = 0.15;
const lonPad = (bounds.maxLon - bounds.minLon) * padding || 0.01;
const latPad = (bounds.maxLat - bounds.minLat) * padding || 0.01;
bounds.minLon -= lonPad;
bounds.maxLon += lonPad;
bounds.minLat -= latPad;
bounds.maxLat += latPad;
STATE.map.bound(bounds, true);
console.log('🗺️ Map fitted to route bounds');
} catch (err) {
console.error('⌠Fit map error:', err);
STATE.map.location({
lon: (STATE.selectedOrigin.lon + STATE.selectedDestination.lon) / 2,
lat: (STATE.selectedOrigin.lat + STATE.selectedDestination.lat) / 2
}, true);
STATE.map.zoom(10, true);
}
}
// ========================================================================================================
// SECTION 11: FARE CALCULATION
// ========================================================================================================
function calculateTaxiFare(distanceKm, timeSeconds) {
let fare = 35;
if (distanceKm > 1) {
let remaining = distanceKm - 1;
const rateSteps = [
{ limit: 1, rate: 6.5 },
{ limit: 8, rate: 6.5 },
{ limit: 10, rate: 7 },
{ limit: 20, rate: 8 },
{ limit: 20, rate: 8.5 },
{ limit: 20, rate: 9 },
{ limit: Infinity, rate: 10.5 }
];
rateSteps.forEach(({ limit, rate }) => {
if (remaining > 0) {
const dist = Math.min(remaining, limit);
fare += dist * rate;
remaining -= dist;
}
});
}
fare += (timeSeconds / 60) * 0;
return Math.round(fare);
}
// ========================================================================================================
// SECTION 12: DISPLAY RESULTS
// ========================================================================================================
function displayRoutes(routeMain, routeFastest) {
const t = TRANSLATIONS[STATE.currentLang];
const main = routeMain.data[0];
const fastest = routeFastest.data[0];
const mainKM = Number(main.distance) / 1000 || 0;
const fastestKM = Number(fastest.distance) / 1000 || 0;
const mainTraffic = STATE.routeDataDebug.main.Distance.data[0];
const mainNoTraffic = STATE.routeDataDebug.main.Traffic.data[0];
const mainExtraTime = Math.max(mainNoTraffic.interval - mainTraffic.interval, 0);
const fastestTraffic = STATE.routeDataDebug.fastest.Distance.data[0];
const fastestNoTraffic = STATE.routeDataDebug.fastest.Traffic.data[0];
const fastestExtraTime = Math.max(fastestNoTraffic.interval - fastestTraffic.interval, 0);
const mainTrafficFare = Math.round((mainExtraTime / 60) * 3);
const fastestTrafficFare = Math.round((fastestExtraTime / 60) * 3);
const mainFare = calculateTaxiFare(mainKM, main.interval) + mainTrafficFare;
const fastestFare = calculateTaxiFare(fastestKM, fastest.interval) + fastestTrafficFare;
const mainTotalTime = main.interval + mainExtraTime;
const fastestTotalTime = fastest.interval + fastestExtraTime;
document.getElementById('dist1').textContent = `${mainKM.toFixed(1)} ${t.km}`;
document.getElementById('time1').textContent = formatTime(mainTotalTime);
document.getElementById('cost1').textContent = `${mainFare} ${t.baht}`;
document.getElementById('dist2').textContent = `${fastestKM.toFixed(1)} ${t.km}`;
document.getElementById('time2').textContent = formatTime(fastestTotalTime);
document.getElementById('cost2').textContent = `${fastestFare} ${t.baht}`;
console.log(`📌 Main(AllDrive): เวลา=${formatTime(mainTotalTime)} ค่าโดยสาร=${mainFare}฿ (รถติด +${mainTrafficFare}฿)`);
console.log(`📌 Fastest(Road): เวลา=${formatTime(fastestTotalTime)} ค่าโดยสาร=${fastestFare}฿ (รถติด +${fastestTrafficFare}฿)`);
window.routeDataForSave = {
origin_lat: STATE.selectedOrigin.lat,
origin_lon: STATE.selectedOrigin.lon,
destination_lat: STATE.selectedDestination.lat,
destination_lon: STATE.selectedDestination.lon,
main_distance_km: mainKM,
main_time_seconds: mainTotalTime,
main_fare: mainFare,
no_tollway_distance_km: fastestKM,
no_tollway_time_seconds: fastestTotalTime,
no_tollway_fare: fastestFare
};
console.log('💾 Route data prepared for saving:', window.routeDataForSave);
sendToTestPage({
origin: {
name: STATE.selectedOrigin.actualName || STATE.selectedOrigin.name || document.getElementById('origin').value,
lat: STATE.selectedOrigin.lat,
lon: STATE.selectedOrigin.lon
},
destination: {
name: STATE.selectedDestination.actualName || STATE.selectedDestination.name || document.getElementById('destination').value,
lat: STATE.selectedDestination.lat,
lon: STATE.selectedDestination.lon
},
mainRoute: {
distance: mainKM.toFixed(1),
time: formatTime(mainTotalTime),
fare: mainFare
},
NoTollwayRoute: {
distance: fastestKM.toFixed(1),
time: formatTime(fastestTotalTime),
fare: fastestFare
}
});
}
function displayDebugInfo() {
const t = TRANSLATIONS[STATE.currentLang];
const mainTraffic = STATE.routeDataDebug.main.Distance.data[0];
const mainNoTraffic = STATE.routeDataDebug.main.Traffic.data[0];
const mainFareTraffic = calculateTaxiFare(mainTraffic.distance / 1000, mainTraffic.interval);
const mainExtraTime = Math.max(mainNoTraffic.interval - mainTraffic.interval, 0);
document.getElementById('debug1_dist_traffic').textContent = `${(mainTraffic.distance / 1000).toFixed(1)} ${t.km}`;
document.getElementById('debug1_time_traffic').textContent = formatTime(mainTraffic.interval);
document.getElementById('debug1_fare_traffic').textContent = `${mainFareTraffic} ${t.baht}`;
document.getElementById('debug1_time_notraffic').textContent = formatTime(mainNoTraffic.interval);
document.getElementById('debug1_extra_time').textContent = formatTime(mainExtraTime);
const fastestTraffic = STATE.routeDataDebug.fastest.Distance.data[0];
const fastestNoTraffic = STATE.routeDataDebug.fastest.Traffic.data[0];
const fastestFareTraffic = calculateTaxiFare(fastestTraffic.distance / 1000, fastestTraffic.interval);
const fastestExtraTime = Math.max(fastestNoTraffic.interval - fastestTraffic.interval, 0);
document.getElementById('debug2_dist_traffic').textContent = `${(fastestTraffic.distance / 1000).toFixed(1)} ${t.km}`;
document.getElementById('debug2_time_traffic').textContent = formatTime(fastestTraffic.interval);
document.getElementById('debug2_fare_traffic').textContent = `${fastestFareTraffic} ${t.baht}`;
document.getElementById('debug2_time_notraffic').textContent = formatTime(fastestNoTraffic.interval);
document.getElementById('debug2_extra_time').textContent = formatTime(fastestExtraTime);
console.log('🔍 Debug info displayed');
}
function formatTime(sec) {
const t = TRANSLATIONS[STATE.currentLang];
const min = Math.round(sec / 60);
const h = Math.floor(min / 60);
const m = min % 60;
return h > 0 ? `${h} ${t.hr} ${m} ${t.min}` : `${m} ${t.min}`;
}
// ========================================================================================================
// SECTION 13: CONFIRM & SAVE
// ========================================================================================================
async function confirmRoute() {
if (!window.routeDataForSave) {
alert('กรุณารอการคำนวณเส้นทางให้เสร็จก่อน');
return;
}
if (!STATE.selectedRouteType) {
alert('กรุณาเลือกเส้นทางก่อนยืนยัน');
return;
}
try {
document.getElementById('confirmBtn').disabled = true;
document.getElementById('confirmBtn').innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>กำลังบันทึก...';
// 🔥 แปลง route_used เป็นชื่อที่ชัดเจน
let routeUsedName;
if (STATE.selectedRouteType === 'main') {
routeUsedName = 'Main Route';
} else if (STATE.selectedRouteType === 'fastest') {
routeUsedName = 'Secondary Route';
} else {
routeUsedName = STATE.selectedRouteType; // fallback
}
const dataToSave = {
// 🔥 ใช้ชื่อภาษาไทย (actualNameThai) สำหรับบันทึกลงฐานข้อมูล
origin_name: STATE.selectedOrigin.actualNameThai || STATE.selectedOrigin.actualName,
origin_lat: window.routeDataForSave.origin_lat,
origin_lon: window.routeDataForSave.origin_lon,
destination_name: STATE.selectedDestination.actualNameThai || STATE.selectedDestination.actualName,
destination_lat: window.routeDataForSave.destination_lat,
destination_lon: window.routeDataForSave.destination_lon,
main_distance_km: window.routeDataForSave.main_distance_km,
main_time_seconds: window.routeDataForSave.main_time_seconds,
main_fare: window.routeDataForSave.main_fare,
no_tollway_distance_km: window.routeDataForSave.no_tollway_distance_km,
no_tollway_time_seconds: window.routeDataForSave.no_tollway_time_seconds,
no_tollway_fare: window.routeDataForSave.no_tollway_fare,
route_used: routeUsedName, // 🔥 'Main route' หรือ 'Secondary route'
route_by: 'passenger', // ระบุว่ามาจากผู้โดยสาร
meter_id: null // ไม่มี meter_id ในหน้าคนขับ
};
console.log('📤 Sending data to server:', dataToSave);
const response = await fetch('save_estimate.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(dataToSave)
});
const result = await response.json();
console.log('📥 Server response:', result);
if (result.success) {
document.getElementById('successMessage').classList.add('active');
document.getElementById('confirmBtn').style.display = 'none';
generateQRCode(result.estimate_id);
setTimeout(() => {
document.getElementById('successMessage').classList.remove('active');
}, 3000);
console.log('✅ บันทึกสำเร็จ ID:', result.estimate_id);
} else {
alert('เกิดข้อผิดพลาด: ' + result.message);
document.getElementById('confirmBtn').disabled = false;
document.getElementById('confirmBtn').innerHTML = '<i class="bi bi-check-circle"></i> <span data-i18n="confirm_btn">ยืนยันเส้นทางนี้</span>';
}
} catch (error) {
console.error('⌠Error:', error);
alert('เกิดข้อผิดพลาดในการบันทึกข้อมูล');
document.getElementById('confirmBtn').disabled = false;
document.getElementById('confirmBtn').innerHTML = '<i class="bi bi-check-circle"></i> <span data-i18n="confirm_btn">ยืนยันเส้นทางนี้</span>';
}
};
// ========================================================================================================
// SECTION 14: QR CODE
// ========================================================================================================
function generateQRCode() {
const qrDiv = document.getElementById('qrcode');
qrDiv.innerHTML = '';
// ดึง URL เฉพาะโฟลเดอร์หลัก เช่น https://example.com/driver
const baseUrl = window.location.href.split('/').slice(0, -1).join('/');
// สร้าง QR Code (ลิงก์ไปหน้า passenger)
new QRCode(qrDiv, {
text: baseUrl + '/passengerqp.php?session=' + CONFIG.SESSION_ID,
width: 180,
height: 180,
colorDark: '#0d1b3a',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H
});
}
function toggleQR() {
const qrBox = document.getElementById('qrcode-box');
if (qrBox.style.display === 'none' || qrBox.style.display === '') {
qrBox.style.display = 'flex';
// เลื่อนหน้าจอไปล่างสุด
setTimeout(() => {
qrBox.scrollIntoView({
behavior: 'smooth',
block: 'end'
});
}, 100);
} else {
qrBox.style.display = 'none';
}
}
// ========================================================================================================
// SECTION 15: TEST PAGE INTEGRATION
// ========================================================================================================
function sendDataToTestPage() {
if (!STATE.selectedOrigin || !STATE.selectedDestination ||
!STATE.routeData.main || !STATE.routeData.fastest) {
console.log('⚠️ Cannot send to test page: Missing data');
return;
}
try {
const mainRoute = STATE.routeData.main.data[0];
const NoTollwayRoute = STATE.routeData.fastest.data[0];
const mainFare = calculateTaxiFare(mainRoute.distance / 1000, mainRoute.interval);
const fastestFare = calculateTaxiFare(NoTollwayRoute.distance / 1000, NoTollwayRoute.interval);
const testData = {
timestamp: new Date().toISOString(),
origin: {
name: STATE.selectedOrigin.actualName || STATE.selectedOrigin.name,
lat: STATE.selectedOrigin.lat.toFixed(6),
lon: STATE.selectedOrigin.lon.toFixed(6)
},
destination: {
name: STATE.selectedDestination.actualName || STATE.selectedDestination.name,
lat: STATE.selectedDestination.lat.toFixed(6),
lon: STATE.selectedDestination.lon.toFixed(6)
},
mainRoute: {
type: 'มีทางด่วน (With Tollway)',
distance: (mainRoute.distance / 1000).toFixed(1),
time: formatTime(mainRoute.interval),
fare: mainFare
},
NoTollwayRoute: {
type: 'ไม่มีทางด่วน (No Tollway)',
distance: (NoTollwayRoute.distance / 1000).toFixed(1),
time: formatTime(NoTollwayRoute.interval),
fare: fastestFare
}
};
const storageKey = 'testRouteData_' + Date.now();
localStorage.setItem(storageKey, JSON.stringify(testData));
console.log('📤 Data sent to test page:', testData);
console.log('🔑 Storage key:', storageKey);
const allKeys = Object.keys(localStorage).filter(key => key.startsWith('testRouteData_'));
if (allKeys.length > 10) {
allKeys.sort();
allKeys.slice(0, allKeys.length - 10).forEach(key => localStorage.removeItem(key));
}
} catch (error) {
console.error('⌠Error sending data to test page:', error);
}
}
function sendToTestPage(data) {
try {
const key = `testRouteData_${Date.now()}`;
localStorage.setItem(key, JSON.stringify(data));
console.log('🧪 Test data sent to localStorage:', key, data);
const allKeys = Object.keys(localStorage)
.filter(k => k.startsWith('testRouteData_'))
.sort();
if (allKeys.length > 5) {
const keysToDelete = allKeys.slice(0, allKeys.length - 5);
keysToDelete.forEach(k => localStorage.removeItem(k));
}
try {
if (window.opener) {
window.opener.postMessage({
type: 'routeData',
data: data
}, '*');
}
} catch (e) {
// Ignore postMessage errors
}
} catch (error) {
console.error('⌠Error sending to test page:', error);
}
}
// ========================================================================================================
// SECTION 16: EVENT LISTENERS
// ========================================================================================================
document.getElementById('origin').addEventListener('input', e => {
toggleClearButton('origin', e.target.value.length > 0);
clearTimeout(STATE.searchTimeout);
STATE.searchTimeout = setTimeout(() =>
searchPlace(e.target.value, document.getElementById('originSuggestions'), true), 300);
});
document.getElementById('destination').addEventListener('input', e => {
toggleClearButton('destination', e.target.value.length > 0);
clearTimeout(STATE.searchTimeout);
STATE.searchTimeout = setTimeout(() =>
searchPlace(e.target.value, document.getElementById('destinationSuggestions'), false), 300);
});
document.addEventListener('click', e => {
if (!e.target.closest('.position-relative')) {
document.getElementById('originSuggestions').style.display = 'none';
document.getElementById('destinationSuggestions').style.display = 'none';
}
});
document.getElementById('origin').addEventListener('focus', () => {
const box = document.getElementById('originSuggestions');
if (!box.innerHTML) displaySuggestions([], box, true);
box.style.display = 'block';
});
document.getElementById('destination').addEventListener('focus', () => {
const box = document.getElementById('destinationSuggestions');
if (!box.innerHTML) displaySuggestions([], box, false);
box.style.display = 'block';
});
// ========================================================================================================
// SECTION 17: INITIALIZATION
// ========================================================================================================
document.addEventListener('DOMContentLoaded', () => {
updateLanguage();
updateLanguageSwitcher();
console.log('✓ DOM ready, language initialized');
});
window.addEventListener('load', async () => {
console.log('🚀 System initializing...');
initMap();
generateQRCode();
await new Promise(resolve => setTimeout(resolve, 500));
const hasPermission = await checkAndRequestGPSPermission();
if (hasPermission) {
setTimeout(() => {
autoDetectCurrentLocation();
}, 300);
}
console.log('✓ System initialized');
});