<!doctype html>
<html lang="th">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>โปรแกรมคำนวณราคาขนส่ง v6 – ทรัพย์สินขนส่ง</title>
<!-- ใส่คีย์ API ของคุณแล้ว ✅ -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyANwrWrjgSiaENrJj692Ft_eDvbgwqAv4M&libraries=places&language=th"></script>
<style>
:root{
--bg:#EAEAEA;
--card:#FFFFFF;
--accent:#F5B041;
--text:#0b0b0b;
--muted:#6b6b6b;
--result:#1D8348;
--copy:#198754;
--error:#dc3545;
}
html,body{margin:0;font-family:"Kanit","Prompt",system-ui;background:var(--bg);color:var(--text);}
body{display:flex;justify-content:center;padding:18px;}
.wrap{max-width:820px;width:100%;}
header{background:#fff;border-radius:12px;padding:16px;margin-bottom:14px;box-shadow:0 3px 12px rgba(0,0,0,0.08);}
.brand{display:flex;align-items:center;gap:14px}
.logo{width:64px;height:64px;border-radius:12px;background:linear-gradient(135deg,#F5B041,#E89A25);display:flex;align-items:center;justify-content:center;color:white;font-weight:800;font-size:20px;}
.card{background:var(--card);padding:16px;border-radius:12px;box-shadow:0 8px 20px rgba(0,0,0,0.08);}
label{font-size:13px;color:var(--muted);}
input,select,textarea{width:100%;padding:12px;border-radius:10px;border:1px solid #ddd;font-size:15px;margin-top:6px;transition:border-color 0.3s;}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent);}
.error{border-color:var(--error) !important;}
.row{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
.btn{width:100%;padding:12px;border:none;border-radius:10px;font-weight:700;font-size:16px;cursor:pointer;margin-top:8px;transition:opacity 0.3s;}
.btn:hover{opacity:0.9;}
.btn-main{background:var(--accent);color:#111;}
.btn-copy{background:var(--copy);color:#fff;}
.btn-share{background:#2ea44f;color:#fff;}
.btn-pdf{background:#0b74de;color:#fff;}
.btn-history{background:#6f42c1;color:#fff;}
.result{background:#fdfdfd;border:1px solid #eee;border-radius:10px;padding:14px;margin-top:14px;}
.big{font-size:20px;font-weight:800;color:var(--result);}
.muted{color:var(--muted);}
.small{font-size:13px;color:var(--muted);}
.options{background:#fafafa;border:1px solid #eee;border-radius:10px;padding:12px;margin-top:10px;}
.radio-group{display:flex;gap:14px;flex-wrap:wrap;}
/* Modal Styles */
.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:1000;justify-content:center;align-items:center;}
.modal-content{background:white;padding:20px;border-radius:12px;width:90%;max-width:600px;max-height:80vh;overflow-y:auto;}
.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;}
.close{font-size:24px;cursor:pointer;}
.history-item{border-bottom:1px solid #eee;padding:10px 0;}
.history-item:last-child{border-bottom:none;}
/* Loading */
#loading{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;justify-content:center;align-items:center;}
.loading-content{background:white;padding:20px;border-radius:10px;text-align:center;}
@media (max-width: 600px) {
.row{grid-template-columns:1fr;}
}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="brand">
<div class="logo">ทร</div>
<div>
<div style="font-size:20px;font-weight:800">ทรัพย์สินขนส่ง</div>
<div style="font-size:13px;color:var(--muted)">โปรแกรมคำนวณราคาขนส่ง • โทร: 096-410-4396</div>
</div>
</div>
</header>
<!-- ส่วนค้นหาระยะทางอัตโนมัติ -->
<div class="card">
<h3 style="margin-top:0">📍 ค้นหาและคำนวณระยะทางอัตโนมัติ</h3>
<div class="row">
<div>
<label>ค้นหาต้นทาง</label>
<input id="autocompletePickup" type="text" placeholder="พิมพ์ตำบล/อำเภอ/จังหวัดต้นทาง" />
<div id="pickupDetails" class="small" style="margin-top:4px;color:#666;"></div>
</div>
<div>
<label>ค้นหาปลายทาง</label>
<input id="autocompleteDropoff" type="text" placeholder="พิมพ์ตำบล/อำเภอ/จังหวัดปลายทาง" />
<div id="dropoffDetails" class="small" style="margin-top:4px;color:#666;"></div>
</div>
</div>
<button id="calcAutoDistance" class="btn" style="background:#6610f2;color:white;margin-top:10px;">
🧮 คำนวณระยะทางอัตโนมัติ
</button>
<div id="distanceResult" style="display:none; margin-top:15px; padding:15px; background:#e8f5e8; border-radius:8px; border:1px solid #28a745;">
<div style="font-weight:700;color:#1D8348;">📏 ระยะทางที่คำนวณได้: <span id="calculatedDistance">0</span> กม.</div>
<div style="color:#666;margin-top:5px;">⏱️ เวลาโดยประมาณ: <span id="calculatedTime">0</span> นาที</div>
<button id="useCalculatedDistance" class="btn" style="background:#28a745;color:white;margin-top:10px;">
✅ ใช้ระยะทางนี้ในการคำนวณ
</button>
</div>
</div>
<!-- ส่วนคำนวณราคาเดิม -->
<div class="card">
<h2 style="margin-top:0">โปรแกรมคำนวณราคาขนส่ง (v6)</h2>
<div class="row">
<div>
<label>ประเภทงาน</label>
<select id="type">
<option value="hire">งานเหมา</option>
<option value="deposit">งานฝาก</option>
</select>
</div>
<div>
<label>ระยะทาง (กม.)</label>
<input id="distance" type="number" placeholder="เช่น 450" />
</div>
</div>
<div class="row" style="margin-top:10px">
<div>
<label>ชื่อลูกค้า</label>
<input id="custName" type="text" placeholder="ชื่อผู้ติดต่อ / ลูกค้า" />
</div>
<div>
<label>ระยะเวลาการจัดส่ง</label>
<input id="deliveryTime" type="text" placeholder="เช่น 1–2 วัน" value="1-2 วัน" />
</div>
</div>
<div class="row" style="margin-top:10px">
<div>
<label>ต้นทาง</label>
<input id="pickup" type="text" placeholder="จุดรับ" />
</div>
<div>
<label>ปลายทาง</label>
<input id="dropoff" type="text" placeholder="จุดส่ง" />
</div>
</div>
<div id="depositOptions" class="options" style="display:none">
<label class="small">หมวดงานฝาก</label>
<div class="radio-group">
<label><input type="radio" name="depositType" value="pet" checked> สัตว์เลี้ยง</label>
<label><input type="radio" name="depositType" value="motorcycle"> มอเตอร์ไซค์</label>
<label><input type="radio" name="depositType" value="half"> สินค้าครึ่งคัน</label>
</div>
<div id="petOptions" style="margin-top:8px">
<label class="small">ขนาดสัตว์เลี้ยง</label>
<div class="radio-group">
<label><input type="radio" name="petSize" value="small" checked> ตัวเล็ก</label>
<label><input type="radio" name="petSize" value="large"> ตัวใหญ่</label>
</div>
</div>
<div id="motorOptions" style="display:none;margin-top:8px">
<label class="small">ประเภทมอเตอร์ไซค์</label>
<div class="radio-group">
<label><input type="radio" name="motorClass" value="small" checked> ≤150cc</label>
<label><input type="radio" name="motorClass" value="mid"> 150–1000cc</label>
<label><input type="radio" name="motorClass" value="big"> บิ๊กไบค์</label>
</div>
</div>
<label style="margin-top:8px" class="small">หมายเหตุเพิ่มเติม</label>
<textarea id="note" rows="2" placeholder="เช่น ต้องมีกรง, สภาพสินค้า ฯลฯ"></textarea>
<label style="margin-top:8px" class="small">มัดจำ (%)</label>
<input id="depositPercent" type="number" value="30" min="10" max="100" />
</div>
<button id="calc" class="btn btn-main">คำนวณราคา</button>
<div id="output" class="result" style="display:none">
<div class="small">ประเภท</div>
<div id="outType" class="muted">-</div>
<div class="small" style="margin-top:6px">ระยะทาง</div>
<div id="outDist" class="big">0 กม.</div>
<div class="small" style="margin-top:6px">ค่าขนส่งรวม</div>
<div id="outPrice" class="big">0 บาท</div>
<div class="small" style="margin-top:6px">มัดจำ</div>
<div id="outDeposit" class="big">0 บาท</div>
<div class="small" style="margin-top:6px">ยอดคงเหลือ</div>
<div id="outRemain" class="big">0 บาท</div>
<button id="copyBtn" class="btn btn-copy">คัดลอกราคา</button>
<button id="shareBtn" class="btn btn-share">แชร์ไป LINE</button>
<button id="pdfBtn" class="btn btn-pdf">สร้างใบเสนอราคา (PDF)</button>
<button id="historyBtn" class="btn btn-history">ดูประวัติการคำนวณ</button>
</div>
</div>
</div>
<!-- Modal สำหรับแสดงประวัติ -->
<div id="historyModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>ประวัติการคำนวณ</h3>
<span class="close">×</span>
</div>
<div id="historyList"></div>
</div>
</div>
<!-- Loading Indicator -->
<div id="loading">
<div class="loading-content">
<div>กำลังประมวลผล...</div>
</div>
</div>
<script>
// ฟังก์ชันคำนวณราคาตามสูตร
function calcHire(d){return d<=100?1800:1800+(d-100)*10;}
function calcPetSmall(d){return d<400?1500:d*3;}
function calcPetLarge(d){return d<400?2500:d*4;}
function calcMotorMid(d){return d<400?2000:d*3.5;}
function calcHalf(d){return d<200?2000:2000+(d-200)*5;}
// ตัวแปรและ Element References
const typeEl = document.getElementById("type");
const depositOptions = document.getElementById("depositOptions");
const petOpt = document.getElementById("petOptions");
const motorOpt = document.getElementById("motorOptions");
const historyModal = document.getElementById("historyModal");
const historyList = document.getElementById("historyList");
const loading = document.getElementById("loading");
// Event Listeners พื้นฐาน
typeEl.addEventListener("change", () => {
depositOptions.style.display = typeEl.value === "deposit" ? "block" : "none";
});
document.querySelectorAll("input[name='depositType']").forEach(r => r.addEventListener("change", () => {
const v = r.value;
petOpt.style.display = v === "pet" ? "block" : "none";
motorOpt.style.display = v === "motorcycle" ? "block" : "none";
}));
// ========== ฟังก์ชันใหม่ที่เพิ่ม ==========
// 1. ฟังก์ชันแจ้งเตือนด้วยเสียง
function speakNotification(message) {
if ('speechSynthesis' in window) {
const speech = new SpeechSynthesisUtterance();
speech.text = message;
speech.lang = 'th-TH';
speech.rate = 0.9;
speech.pitch = 1.0;
speech.volume = 1.0;
window.speechSynthesis.cancel();
window.speechSynthesis.speak(speech);
}
}
// 2. ฟังก์ชันตรวจสอบข้อมูล
function validateForm() {
const required = [
{id: 'distance', name: 'ระยะทาง'},
{id: 'custName', name: 'ชื่อลูกค้า'},
{id: 'pickup', name: 'จุดรับสินค้า'},
{id: 'dropoff', name: 'จุดส่งสินค้า'}
];
const missing = required.filter(field => {
const element = document.getElementById(field.id);
return !element.value.trim();
});
if (missing.length > 0) {
const fieldNames = missing.map(m => m.name).join(', ');
const message = `กรุณากรอกข้อมูลให้ครบ: ${fieldNames}`;
speakNotification(message);
// เน้น input ที่ขาด
missing.forEach(field => {
document.getElementById(field.id).classList.add('error');
});
return false;
}
// รีเซ็ตสี border
required.forEach(field => {
document.getElementById(field.id).classList.remove('error');
});
return true;
}
// 3. ฟังก์ชันบันทึกประวัติ
function saveToHistory(data) {
try {
const history = JSON.parse(localStorage.getItem('transportHistory') || '[]');
history.unshift({
...data,
timestamp: new Date().toISOString(),
id: Date.now()
});
// เก็บแค่ 50 รายการล่าสุด
if (history.length > 50) history.pop();
localStorage.setItem('transportHistory', JSON.stringify(history));
console.log('บันทึกประวัติเรียบร้อย');
} catch (error) {
console.error('Error saving history:', error);
}
}
// 4. ฟังก์ชันโหลดประวัติ
function loadHistory() {
try {
return JSON.parse(localStorage.getItem('transportHistory') || '[]');
} catch (error) {
console.error('Error loading history:', error);
return [];
}
}
// 5. ฟังก์ชันแสดงประวัติ
function showHistory() {
const history = loadHistory();
if (history.length === 0) {
historyList.innerHTML = '<p>ยังไม่มีประวัติการคำนวณ</p>';
} else {
historyList.innerHTML = history.map(item => `
<div class="history-item">
<div><strong>${item.customer}</strong> - ${item.type}</div>
<div>${item.pickup} → ${item.dropoff}</div>
<div>${item.distance} กม. - ฿${item.price.toLocaleString()}</div>
<div class="small">${new Date(item.timestamp).toLocaleString('th-TH')}</div>
</div>
`).join('');
}
historyModal.style.display = 'flex';
}
// 6. ฟังก์ชันแสดง/ซ่อน Loading
function showLoading() {
loading.style.display = 'flex';
}
function hideLoading() {
loading.style.display = 'none';
}
// ========== GOOGLE MAPS AUTOMATIC DISTANCE CALCULATION ==========
// ตัวแปร global
let autocompletePickup, autocompleteDropoff;
let calculatedDistance = 0;
let selectedPickup = '', selectedDropoff = '';
// 1. ฟังก์ชันเริ่มต้น Autocomplete
function initAutocomplete() {
console.log('กำลังเริ่มต้น Google Places Autocomplete...');
try {
// Autocomplete สำหรับจุดรับ
autocompletePickup = new google.maps.places.Autocomplete(
document.getElementById('autocompletePickup'),
{
types: ['geocode'],
componentRestrictions: { country: 'th' },
fields: ['address_components', 'formatted_address', 'geometry']
}
);
// Autocomplete สำหรับจุดส่ง
autocompleteDropoff = new google.maps.places.Autocomplete(
document.getElementById('autocompleteDropoff'),
{
types: ['geocode'],
componentRestrictions: { country: 'th' },
fields: ['address_components', 'formatted_address', 'geometry']
}
);
// เมื่อเลือกสถานที่จาก autocomplete
autocompletePickup.addListener('place_changed', function() {
onPlaceChanged('pickup');
});
autocompleteDropoff.addListener('place_changed', function() {
onPlaceChanged('dropoff');
});
console.log('Google Places Autocomplete พร้อมใช้งานแล้ว');
speakNotification('ระบบค้นหาที่อยู่พร้อมใช้งาน');
} catch (error) {
console.error('Error initializing Autocomplete:', error);
speakNotification('เกิดข้อผิดพลาดในการเริ่มต้นระบบค้นหา');
}
}
// 2. ฟังก์ชันเมื่อเลือกสถานที่
function onPlaceChanged(type) {
const autocomplete = type === 'pickup' ? autocompletePickup : autocompleteDropoff;
const place = autocomplete.getPlace();
const detailsElement = document.getElementById(type + 'Details');
if (!place.geometry) {
detailsElement.textContent = 'ไม่พบสถานที่ กรุณาพิมพ์ใหม่';
detailsElement.style.color = 'red';
return;
}
// แยกส่วนที่อยู่
let district = '', amphoe = '', province = '';
for (const component of place.address_components) {
const types = component.types;
if (types.includes('sublocality_level_1') || types.includes('sublocality')) {
district = component.long_name;
} else if (types.includes('administrative_area_level_2')) {
amphoe = component.long_name;
} else if (types.includes('administrative_area_level_1')) {
province = component.long_name;
}
}
const addressDetails = `${district} ${amphoe ? ', ' + amphoe : ''} ${province ? ', ' + province : ''}`;
detailsElement.textContent = addressDetails;
detailsElement.style.color = '#1D8348';
if (type === 'pickup') {
selectedPickup = place.formatted_address;
} else {
selectedDropoff = place.formatted_address;
}
speakNotification(`เลือก${type === 'pickup' ? 'ต้นทาง' : 'ปลายทาง'}เรียบร้อย`);
}
// 3. ฟังก์ชันคำนวณระยะทาง
function calculateDistance() {
const origin = document.getElementById('autocompletePickup').value;
const destination = document.getElementById('autocompleteDropoff').value;
if (!origin || !destination) {
const message = 'กรุณาเลือกทั้งต้นทางและปลายทาง';
alert(message);
speakNotification(message);
return;
}
showLoading();
speakNotification('กำลังคำนวณระยะทาง...');
const service = new google.maps.DistanceMatrixService();
service.getDistanceMatrix(
{
origins: [selectedPickup || origin],
destinations: [selectedDropoff || destination],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC,
avoidHighways: false,
avoidTolls: false,
},
function(response, status) {
hideLoading();
if (status === 'OK') {
const element = response.rows[0].elements[0];
if (element.status === 'OK') {
const distance = element.distance.value / 1000; // แปลงเป็นกิโลเมตร
const duration = Math.round(element.duration.value / 60); // แปลงเป็นนาที
calculatedDistance = Math.round(distance);
document.getElementById('calculatedDistance').textContent = calculatedDistance.toLocaleString();
document.getElementById('calculatedTime').textContent = duration.toLocaleString();
document.getElementById('distanceResult').style.display = 'block';
// Scroll ให้เห็นผลลัพธ์
document.getElementById('distanceResult').scrollIntoView({
behavior: 'smooth',
block: 'center'
});
speakNotification(`คำนวณระยะทางเสร็จสิ้น ${calculatedDistance} กิโลเมตร ใช้เวลาประมาณ ${duration} นาที`);
} else {
const message = 'ไม่สามารถคำนวณระยะทางได้ กรุณาลองใหม่อีกครั้ง';
alert(message);
speakNotification(message);
}
} else {
const message = 'เกิดข้อผิดพลาดในการเชื่อมต่อ: ' + status;
alert(message);
speakNotification('เกิดข้อผิดพลาดในการคำนวณ');
}
}
);
}
// 4. ฟังก์ชันใช้ระยะทางที่คำนวณได้
function useCalculatedDistance() {
if (calculatedDistance === 0) {
alert('กรุณาคำนวณระยะทางก่อน');
return;
}
// อัพเดทฟอร์มหลัก
document.getElementById('distance').value = calculatedDistance;
document.getElementById('pickup').value = document.getElementById('autocompletePickup').value;
document.getElementById('dropoff').value = document.getElementById('autocompleteDropoff').value;
// ซ่อนผลลัพธ์
document.getElementById('distanceResult').style.display = 'none';
speakNotification(`ตั้งค่าระยะทาง ${calculatedDistance} กิโลเมตรเรียบร้อย พร้อมคำนวณราคา`);
// คำนวณราคาอัตโนมัติหลังจากหน่วงเวลาเล็กน้อย
setTimeout(() => {
document.getElementById('calc').click();
}, 1500);
}
// 5. ฟังก์ชันตรวจสอบและปรับปรุงสำหรับมือถือ Xiaomi
function optimizeForXiaomi() {
const userAgent = navigator.userAgent.toLowerCase();
const isXiaomi = userAgent.in