diff --git a/hotel-data-status.js b/hotel-data-status.js index 2fda602..cff2b21 100644 --- a/hotel-data-status.js +++ b/hotel-data-status.js @@ -18,23 +18,16 @@ function applyPendingState(data) { const box = ensureStatusBox(); - if (box) { - box.innerHTML = '⚠️ Chưa có snapshot live cho flow mới. Preview không dùng danh sách fallback giả. Sau khi merge, workflow Hotel sẽ tự discovery Google Hotels và ghi dữ liệu thật.'; - } + if (box) box.innerHTML = 'Dữ liệu khách sạn đang được cập nhật. Vui lòng quay lại sau ít phút.'; - if ($('discoveryMeta')) { - const maxPages = Number(data?.search?.max_pages || 0); - $('discoveryMeta').textContent = `0/${maxPages || '—'} trang API · chưa chạy live refresh`; - } - if ($('hotelResults')) { - $('hotelResults').innerHTML = '
Chưa có dữ liệu Google Hotels live.
Danh sách khách sạn sẽ xuất hiện sau lần workflow cập nhật đầu tiên.
'; - } - if ($('resultCount')) $('resultCount').textContent = '0 khách sạn · chờ refresh'; + if ($('discoveryMeta')) $('discoveryMeta').textContent = 'Đang cập nhật danh sách khách sạn'; + if ($('hotelResults')) $('hotelResults').innerHTML = '
Danh sách khách sạn đang được cập nhật.
'; + if ($('resultCount')) $('resultCount').textContent = 'Đang cập nhật'; if ($('cheapestPrice')) $('cheapestPrice').textContent = '—'; - if ($('cheapestName')) $('cheapestName').textContent = 'Chưa có snapshot live'; + if ($('cheapestName')) $('cheapestName').textContent = 'Chưa có dữ liệu giá'; if ($('groupEstimate')) $('groupEstimate').textContent = '—'; - if ($('updatedAgo')) $('updatedAgo').textContent = 'Chưa chạy'; - if ($('updatedAt')) $('updatedAt').textContent = 'Workflow sẽ chạy sau khi merge'; + if ($('updatedAgo')) $('updatedAgo').textContent = 'Đang cập nhật'; + if ($('updatedAt')) $('updatedAt').textContent = ''; } function applyLegacyWarning(data) { @@ -42,9 +35,7 @@ const legacy = properties.some(item => item?.catalogue_fallback || String(item?.id || '').startsWith('fallback:')); if (!legacy && data?.search?.query === 'Shanghai hotels') return false; const box = ensureStatusBox(); - if (box) { - box.innerHTML = '⚠️ Snapshot cũ. Dữ liệu này chưa được tạo từ discovery “Shanghai hotels”; không nên dùng để đánh giá giá hoặc khoảng cách.'; - } + if (box) box.innerHTML = 'Dữ liệu đang được làm mới. Một số giá hoặc thông tin có thể chưa cập nhật.'; return true; } @@ -90,4 +81,8 @@ } init(); + + const copyScript = document.createElement('script'); + copyScript.src = './hotel-ui-copy.js'; + document.head.appendChild(copyScript); })(); diff --git a/hotel-ui-copy.js b/hotel-ui-copy.js new file mode 100644 index 0000000..db6ad11 --- /dev/null +++ b/hotel-ui-copy.js @@ -0,0 +1,132 @@ +(() => { + const DATA_URL = './data/hotels.json'; + const $ = id => document.getElementById(id); + let data = null; + + function setText(node, value) { + if (node && node.textContent !== value) node.textContent = value; + } + + function replaceText(root, from, to) { + if (!root) return; + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); + const nodes = []; + while (walker.nextNode()) nodes.push(walker.currentNode); + nodes.forEach(node => { + if (node.nodeValue.includes(from)) node.nodeValue = node.nodeValue.replaceAll(from, to); + }); + } + + function formatCount(value) { + const n = Number(value || 0); + return Number.isFinite(n) ? new Intl.NumberFormat('vi-VN').format(n) : '0'; + } + + function applyStaticCopy() { + setText(document.querySelector('.hotel-heading .hotel-kicker'), 'Khách sạn'); + setText(document.querySelector('.hotel-heading h1'), 'Khách sạn tại Shanghai'); + setText(document.querySelector('.hotel-heading p'), 'So sánh giá, vị trí và đánh giá cho đêm 19–20/10/2026. Khoảng cách được tính từ 531 Jinling East Road để bạn dễ chọn nơi lưu trú phù hợp.'); + setText(document.querySelector('.hotel-results-head .hotel-kicker'), 'Danh sách'); + setText(document.querySelector('.hotel-results-head h2'), 'Khách sạn phù hợp'); + + const note = document.querySelector('.hotel-note:not(#hotelDataStatus)'); + setText(note, 'Giá hiển thị dành cho 2 người lớn/phòng và có thể thay đổi theo hạng phòng, thuế, phí hoặc tình trạng phòng tại thời điểm đặt.'); + + setText(document.querySelector('.hotel-history .hotel-kicker'), 'Theo dõi giá'); + setText(document.querySelector('.hotel-history h2'), 'Lịch sử giá'); + + const historyMeta = $('historyMeta'); + if (historyMeta && /workflow|lần ghi nhận|Mỗi điểm/i.test(historyMeta.textContent || '')) { + setText(historyMeta, 'Theo dõi biến động giá của khách sạn đã chọn.'); + } + + const targetCard = document.querySelector('.hotel-target-card > p'); + setText(targetCard, 'Đặt mức giá mong muốn để dễ theo dõi.'); + + const groupStat = [...document.querySelectorAll('.hotel-stat')].find(card => /Ước tính 3 phòng|Tổng 3 phòng/i.test(card.textContent || '')); + if (groupStat) { + setText(groupStat.querySelector('span'), 'Tổng 3 phòng'); + setText(groupStat.querySelector('small'), '6 người lớn'); + } + } + + function applyResultCopy() { + const results = $('hotelResults'); + if (!results) return; + replaceText(results, 'Google Hotels chưa trả giá', 'Chưa có giá'); + replaceText(results, 'đang chờ Google Hotels', 'Tạm chưa có giá'); + replaceText(results, 'Chưa có rating', 'Chưa có đánh giá'); + replaceText(results, ' review', ' đánh giá'); + replaceText(results, 'Ước tính 3 phòng cho 6 người', '3 phòng / 6 người'); + } + + function applyDetailCopy() { + const detail = document.getElementById('hotelDetailBackdrop'); + if (!detail) return; + replaceText(detail, 'Google Hotels chưa trả ảnh cho khách sạn này trong snapshot hiện tại.', 'Hiện chưa có ảnh cho khách sạn này.'); + replaceText(detail, 'Thông tin chi tiết vẫn có thể xem bên dưới.', 'Bạn vẫn có thể xem thông tin khách sạn bên dưới.'); + replaceText(detail, 'Google Hotels chưa trả giá live', 'Tạm chưa có giá'); + replaceText(detail, ' review', ' đánh giá'); + replaceText(detail, 'Gallery và chi tiết lấy từ snapshot Google Hotels hiện tại; mở modal không phát sinh thêm SerpApi request.', 'Thông tin và hình ảnh được cập nhật từ nguồn dữ liệu khách sạn hiện có.'); + replaceText(detail, 'Nguồn giá đang có', 'Giá từ các nguồn'); + } + + function applyDataCopy() { + if (!data || data.status === 'pending_refresh') return; + const properties = Array.isArray(data.properties) ? data.properties : []; + const search = data.search || {}; + const visible = Number(search.displayed_property_count ?? properties.length ?? 0); + const priced = Number(search.priced_property_count ?? 0); + const value = `${formatCount(visible)} khách sạn · ${formatCount(priced)} có giá`; + setText($('discoveryMeta'), value); + } + + function professionalStatusCopy() { + const box = $('hotelDataStatus'); + if (!box) return; + const raw = box.textContent || ''; + if (/Chưa có snapshot live|flow mới|workflow Hotel|fallback giả|chưa chạy live refresh/i.test(raw)) { + box.innerHTML = 'Dữ liệu khách sạn đang được cập nhật. Vui lòng quay lại sau ít phút.'; + } else if (/Snapshot cũ|discovery/i.test(raw)) { + box.innerHTML = 'Dữ liệu đang được làm mới. Một số giá hoặc thông tin có thể chưa cập nhật.'; + } + } + + async function loadData() { + try { + const response = await fetch(`${DATA_URL}?copy=${Date.now()}`, { cache: 'no-store' }); + if (response.ok) data = await response.json(); + } catch {} + applyDataCopy(); + professionalStatusCopy(); + } + + applyStaticCopy(); + applyResultCopy(); + applyDetailCopy(); + loadData(); + + const results = $('hotelResults'); + if (results) { + const resultsObserver = new MutationObserver(() => { + resultsObserver.disconnect(); + applyResultCopy(); + resultsObserver.observe(results, { childList: true }); + }); + resultsObserver.observe(results, { childList: true }); + } + + document.addEventListener('click', event => { + if (event.target.closest('[data-hotel-detail]')) { + setTimeout(applyDetailCopy, 0); + } + }); + + setTimeout(() => { + applyStaticCopy(); + applyResultCopy(); + applyDetailCopy(); + applyDataCopy(); + professionalStatusCopy(); + }, 300); +})();