เทียบราคาวัสดุก่อสร้าง

ฐานข้อมูลราคาตลาดวัสดุก่อสร้างจากร้านชั้นนำ

ราคาวัสดุกลาง

กำลังโหลด...

ค้นหาวัสดุจากร้านค้า

ค้นเร็ว:
`; const w = window.open('', '_blank'); if (w) { w.document.write(po); w.document.close(); setTimeout(() => w.print(), 300); } else { alert('กรุณาอนุญาต Pop-up เพื่อพิมพ์ใบสั่งซื้อครับ'); } } /* ── Price History modal (เดิมมี UI แต่ไม่มีตัวเปิด/ตัวโหลดข้อมูลเลย) ────────── */ var _histChart = null; async function openPriceHistory(sku, name, platform){ if(!sku){ _toast('สินค้านี้ยังไม่มีรหัส SKU สำหรับดูประวัติราคา', false); return; } openPriceAlertFor(sku, name, platform); // ผูกบริบทให้ปุ่มตั้งเตือน document.getElementById('history-modal-title').textContent = name || 'ประวัติราคา'; document.getElementById('modal-price-history').classList.remove('hidden'); var noData = document.getElementById('history-no-data'); var canvas = document.getElementById('history-chart'); var bar = document.getElementById('history-trend-bar'); try{ var r = await fetch('/api/prices/' + encodeURIComponent(sku) + '/history?days=90'); if(!r.ok) throw new Error('load failed'); var d = await r.json(); var hist = d.history || []; if(!hist.length){ bar.classList.add('hidden'); canvas.classList.add('hidden'); noData.classList.remove('hidden'); return; } // สรุปต่ำสุด 90 วัน / 30 วัน / แนวโน้ม var lows90 = hist.map(function(h){ return h.min_price; }); var cutoff = new Date(Date.now() - 30*86400000).toISOString().slice(0,10); var lows30 = hist.filter(function(h){ return h.day >= cutoff; }).map(function(h){ return h.min_price; }); var fmtB = function(v){ return v ? '฿' + Number(v).toLocaleString('th-TH') : '—'; }; document.getElementById('hist-low-90').textContent = fmtB(Math.min.apply(null, lows90)); document.getElementById('hist-low-30').textContent = lows30.length ? fmtB(Math.min.apply(null, lows30)) : '—'; var tr = (d.summary || {}).trend; document.getElementById('hist-trend-icon').textContent = tr === 'up' ? 'ขึ้น' : tr === 'down' ? 'ลง' : 'ทรงตัว'; bar.classList.remove('hidden'); // กราฟ: เส้นละ platform noData.classList.add('hidden'); canvas.classList.remove('hidden'); var days = Array.from(new Set(hist.map(function(h){ return h.day; }))).sort(); var plats = Array.from(new Set(hist.map(function(h){ return h.platform; }))); var colors = ['#8B6C4A','#B91C1C','#1565c0','#2e7d32','#e65100']; var datasets = plats.map(function(p, i){ return { label: p, data: days.map(function(dy){ var f = hist.find(function(h){ return h.day === dy && h.platform === p; }); return f ? f.min_price : null; }), borderColor: colors[i % colors.length], backgroundColor: 'transparent', tension: .3, spanGaps: true, pointRadius: 2 }; }); if(_histChart){ _histChart.destroy(); } _histChart = new Chart(canvas.getContext('2d'), { type: 'line', data: { labels: days, datasets: datasets }, options: { responsive:true, maintainAspectRatio:false, plugins:{ legend:{ labels:{ boxWidth:10, font:{size:10} } } }, scales:{ y:{ ticks:{ font:{size:10} } }, x:{ ticks:{ font:{size:9}, maxTicksLimit:6 } } } } }); }catch(e){ bar.classList.add('hidden'); canvas.classList.add('hidden'); noData.classList.remove('hidden'); noData.innerHTML = 'โหลดประวัติราคาไม่สำเร็จ
กรุณาลองใหม่อีกครั้ง'; } } /* ── Price Alert (เดิม submitAlert ตายอยู่ — เติมให้ทำงานกับ /api/price-alerts) ── */ var _alertCtx = { sku: '', name: '', platform: '' }; // เรียกตอนเปิด modal ประวัติราคาของสินค้า เพื่อผูกบริบทสินค้าปัจจุบัน window.openPriceAlertFor = function (sku, name, platform) { _alertCtx = { sku: sku || '', name: name || '', platform: platform || '' }; var el = document.getElementById('history-modal-sku'); if (el && sku) el.textContent = 'SKU: ' + sku; loadMyAlerts(); }; function _toast(msg, ok) { if (window.showToast) window.showToast(msg, ok ? 'success' : 'error'); else alert(msg); } async function submitAlert() { // ใช้ sku จากบริบทที่เปิดไว้ หรือ parse จาก element ถ้ามี var sku = _alertCtx.sku; if (!sku) { var el = document.getElementById('history-modal-sku'); if (el && el.textContent) sku = el.textContent.replace(/^SKU:\s*/, '').trim(); } if (!sku) { _toast('กรุณาเลือกสินค้าก่อนตั้งการแจ้งเตือน', false); return; } var target = parseFloat((document.getElementById('alert-target-price') || {}).value || 0); if (!target || target <= 0) { _toast('กรุณาระบุราคาเป้าหมาย', false); return; } var direction = (document.getElementById('alert-direction') || {}).value || 'below'; try { var r = await fetch('/api/price-alerts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ sku: sku, target_price: target, direction: direction, platform: _alertCtx.platform, product_name: _alertCtx.name }) }); var d = await r.json().catch(function () { return {}; }); if (r.status === 401) { _toast('กรุณาเข้าสู่ระบบก่อนตั้งการแจ้งเตือน', false); return; } if (!r.ok) { _toast(d.error || 'ตั้งการแจ้งเตือนไม่สำเร็จ', false); return; } _toast('ตั้งการแจ้งเตือนราคาแล้ว', true); var inp = document.getElementById('alert-target-price'); if (inp) inp.value = ''; loadMyAlerts(); } catch (e) { _toast('เกิดข้อผิดพลาด', false); } } async function loadMyAlerts() { var sec = document.getElementById('my-alerts-section'); var list = document.getElementById('my-alerts-list'); if (!list) return; try { var r = await fetch('/api/price-alerts', { credentials: 'same-origin' }); if (!r.ok) return; var d = await r.json(); var items = (d.alerts || d.price_alerts || []).filter(function (a) { return !_alertCtx.sku || a.sku === _alertCtx.sku; }); if (!items.length) { if (sec) sec.classList.add('hidden'); return; } if (sec) sec.classList.remove('hidden'); list.innerHTML = items.map(function (a) { var dirTh = a.direction === 'above' ? 'สูงกว่า' : 'ต่ำกว่า'; return '
' + 'แจ้งเมื่อราคา ' + dirTh + ' ฿' + Number(a.target_price).toLocaleString() + '' + '' + '
'; }).join(''); } catch (e) {} } async function deleteAlert(id) { try { await fetch('/api/price-alerts/' + id, { method: 'DELETE', credentials: 'same-origin' }); loadMyAlerts(); } catch (e) {} }
รู้หรือไม่
ลองเลย