update weather widget
This commit is contained in:
1 parent
8fe3eea7eb
commit
69c67976c7
3 files changed
+156
-10
No files matched your search
+32
@@ -376,6 +376,38 @@
|
|||||||
<p class="text-gray-600 text-lg" data-i18n="weather_desc">Lên kế hoạch cho chuyến đi của bạn với thông tin thời tiết chính xác.</p>
|
<p class="text-gray-600 text-lg" data-i18n="weather_desc">Lên kế hoạch cho chuyến đi của bạn với thông tin thời tiết chính xác.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 max-w-4xl mx-auto mb-12">
|
||||||
|
<article class="bg-gradient-to-br from-sky-50 to-white border border-sky-100 p-6 sm:p-8 rounded-3xl shadow-sm">
|
||||||
|
<h3 class="text-lg font-bold text-gray-900 mb-5" data-i18n="weather_current">Thời tiết hiện tại</h3>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<i id="weather-current-icon" class="fa-solid fa-cloud-sun text-4xl text-amber-500" aria-hidden="true"></i>
|
||||||
|
<div>
|
||||||
|
<div id="weather-current-temperature" class="text-3xl font-extrabold text-gray-900">--°C</div>
|
||||||
|
<div id="weather-current-condition" class="text-sm text-gray-600" data-i18n="weather_loading" aria-live="polite">Đang tải thời tiết...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-5 text-sm text-gray-600">
|
||||||
|
<span><span data-i18n="weather_high">Cao nhất</span>: <span id="weather-today-high">--°</span></span>
|
||||||
|
<span><span data-i18n="weather_low">Thấp nhất</span>: <span id="weather-today-low">--°</span></span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bg-gradient-to-br from-indigo-50 to-white border border-indigo-100 p-6 sm:p-8 rounded-3xl shadow-sm">
|
||||||
|
<div class="flex items-baseline justify-between gap-3 mb-5">
|
||||||
|
<h3 class="text-lg font-bold text-gray-900" data-i18n="weather_tomorrow">Ngày mai</h3>
|
||||||
|
<time id="weather-tomorrow-date" class="text-sm text-gray-500"></time>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<i id="weather-tomorrow-icon" class="fa-solid fa-cloud-sun text-4xl text-amber-500" aria-hidden="true"></i>
|
||||||
|
<div id="weather-tomorrow-condition" class="text-sm text-gray-600" data-i18n="weather_loading" aria-live="polite">Đang tải thời tiết...</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-5 text-sm text-gray-600">
|
||||||
|
<span><span data-i18n="weather_high">Cao nhất</span>: <span id="weather-tomorrow-high">--°</span></span>
|
||||||
|
<span><span data-i18n="weather_low">Thấp nhất</span>: <span id="weather-tomorrow-low">--°</span></span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||||
<!-- Dry Season -->
|
<!-- Dry Season -->
|
||||||
<div class="bg-gradient-to-br from-amber-50 to-orange-50 border border-amber-200/60 p-8 rounded-3xl shadow-sm space-y-4">
|
<div class="bg-gradient-to-br from-amber-50 to-orange-50 border border-amber-200/60 p-8 rounded-3xl shadow-sm space-y-4">
|
||||||
|
|||||||
+100
-10
@@ -11,6 +11,7 @@ const languageLocales = {
|
|||||||
|
|
||||||
let currentLang = 'vi';
|
let currentLang = 'vi';
|
||||||
let currentWeatherCondition = 'weather_loading';
|
let currentWeatherCondition = 'weather_loading';
|
||||||
|
let tomorrowWeatherCondition = 'weather_loading';
|
||||||
|
|
||||||
function getWeatherPresentation(code) {
|
function getWeatherPresentation(code) {
|
||||||
if (code === 0) return { key: 'weather_clear', icon: 'fa-sun', color: 'text-amber-500' };
|
if (code === 0) return { key: 'weather_clear', icon: 'fa-sun', color: 'text-amber-500' };
|
||||||
@@ -29,25 +30,104 @@ function getWeatherPresentation(code) {
|
|||||||
function renderWeatherCondition() {
|
function renderWeatherCondition() {
|
||||||
const condition = document.getElementById('weather-condition');
|
const condition = document.getElementById('weather-condition');
|
||||||
if (condition) condition.textContent = translations[currentLang][currentWeatherCondition] || translations.en[currentWeatherCondition];
|
if (condition) condition.textContent = translations[currentLang][currentWeatherCondition] || translations.en[currentWeatherCondition];
|
||||||
|
|
||||||
|
const currentForecastCondition = document.getElementById('weather-current-condition');
|
||||||
|
if (currentForecastCondition) {
|
||||||
|
currentForecastCondition.textContent = translations[currentLang][currentWeatherCondition] || translations.en[currentWeatherCondition];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadCurrentWeather() {
|
const tomorrowForecastCondition = document.getElementById('weather-tomorrow-condition');
|
||||||
|
if (tomorrowForecastCondition) {
|
||||||
|
tomorrowForecastCondition.textContent = translations[currentLang][tomorrowWeatherCondition] || translations.en[tomorrowWeatherCondition];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWeatherRange(id, value) {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) element.textContent = Number.isFinite(value) ? `${Math.round(value)}°` : '--°';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWeatherIcon(id, presentation) {
|
||||||
|
const icon = document.getElementById(id);
|
||||||
|
if (icon) icon.className = `fa-solid ${presentation.icon} text-4xl ${presentation.color}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWeatherForecast() {
|
||||||
const temperature = document.getElementById('weather-temperature');
|
const temperature = document.getElementById('weather-temperature');
|
||||||
const icon = document.getElementById('weather-icon');
|
const icon = document.getElementById('weather-icon');
|
||||||
|
const currentForecastTemperature = document.getElementById('weather-current-temperature');
|
||||||
|
const tomorrowDate = document.getElementById('weather-tomorrow-date');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=12.2388&longitude=109.1967¤t=temperature_2m,weather_code&timezone=Asia%2FHo_Chi_Minh');
|
const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=12.2388&longitude=109.1967¤t=temperature_2m,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=Asia%2FHo_Chi_Minh&forecast_days=2');
|
||||||
if (!response.ok) throw new Error('Weather request failed');
|
if (!response.ok) throw new Error('Weather request failed');
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const presentation = getWeatherPresentation(data.current.weather_code);
|
const current = data.current;
|
||||||
temperature.textContent = `${Math.round(data.current.temperature_2m)}°C`;
|
const daily = data.daily;
|
||||||
icon.className = `fa-solid ${presentation.icon} text-2xl ${presentation.color}`;
|
if (
|
||||||
currentWeatherCondition = presentation.key;
|
!Number.isFinite(current?.temperature_2m)
|
||||||
|
|| !Number.isFinite(current?.weather_code)
|
||||||
|
|| !Array.isArray(daily?.time)
|
||||||
|
|| !Array.isArray(daily?.weather_code)
|
||||||
|
|| !Array.isArray(daily?.temperature_2m_max)
|
||||||
|
|| !Array.isArray(daily?.temperature_2m_min)
|
||||||
|
|| daily.time.length < 2
|
||||||
|
|| daily.weather_code.length < 2
|
||||||
|
|| daily.temperature_2m_max.length < 2
|
||||||
|
|| daily.temperature_2m_min.length < 2
|
||||||
|
|| !Number.isFinite(daily.weather_code[1])
|
||||||
|
|| !Number.isFinite(daily.temperature_2m_max[0])
|
||||||
|
|| !Number.isFinite(daily.temperature_2m_min[0])
|
||||||
|
|| !Number.isFinite(daily.temperature_2m_max[1])
|
||||||
|
|| !Number.isFinite(daily.temperature_2m_min[1])
|
||||||
|
|| !/^\d{4}-\d{2}-\d{2}$/.test(daily.time[1])
|
||||||
|
) {
|
||||||
|
throw new Error('Weather response is incomplete');
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPresentation = getWeatherPresentation(current.weather_code);
|
||||||
|
const tomorrowPresentation = getWeatherPresentation(daily.weather_code[1]);
|
||||||
|
const currentTemperature = `${Math.round(current.temperature_2m)}°C`;
|
||||||
|
if (temperature) temperature.textContent = currentTemperature;
|
||||||
|
if (currentForecastTemperature) currentForecastTemperature.textContent = currentTemperature;
|
||||||
|
if (icon) icon.className = `fa-solid ${currentPresentation.icon} text-2xl ${currentPresentation.color}`;
|
||||||
|
setWeatherIcon('weather-current-icon', currentPresentation);
|
||||||
|
setWeatherIcon('weather-tomorrow-icon', tomorrowPresentation);
|
||||||
|
setWeatherRange('weather-today-high', daily.temperature_2m_max[0]);
|
||||||
|
setWeatherRange('weather-today-low', daily.temperature_2m_min[0]);
|
||||||
|
setWeatherRange('weather-tomorrow-high', daily.temperature_2m_max[1]);
|
||||||
|
setWeatherRange('weather-tomorrow-low', daily.temperature_2m_min[1]);
|
||||||
|
|
||||||
|
if (tomorrowDate) {
|
||||||
|
const [year, month, day] = daily.time[1].split('-').map(Number);
|
||||||
|
const date = new Date(year, month - 1, day, 12);
|
||||||
|
tomorrowDate.dateTime = daily.time[1];
|
||||||
|
tomorrowDate.textContent = new Intl.DateTimeFormat(languageLocales[currentLang].replace('_', '-'), {
|
||||||
|
weekday: 'short',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentWeatherCondition = currentPresentation.key;
|
||||||
|
tomorrowWeatherCondition = tomorrowPresentation.key;
|
||||||
} catch {
|
} catch {
|
||||||
temperature.textContent = '--°C';
|
if (temperature) temperature.textContent = '--°C';
|
||||||
icon.className = 'fa-solid fa-cloud text-2xl text-gray-400';
|
if (currentForecastTemperature) currentForecastTemperature.textContent = '--°C';
|
||||||
|
if (icon) icon.className = 'fa-solid fa-cloud text-2xl text-gray-400';
|
||||||
|
setWeatherIcon('weather-current-icon', { icon: 'fa-cloud', color: 'text-gray-400' });
|
||||||
|
setWeatherIcon('weather-tomorrow-icon', { icon: 'fa-cloud', color: 'text-gray-400' });
|
||||||
|
setWeatherRange('weather-today-high', NaN);
|
||||||
|
setWeatherRange('weather-today-low', NaN);
|
||||||
|
setWeatherRange('weather-tomorrow-high', NaN);
|
||||||
|
setWeatherRange('weather-tomorrow-low', NaN);
|
||||||
|
if (tomorrowDate) {
|
||||||
|
tomorrowDate.textContent = '';
|
||||||
|
tomorrowDate.removeAttribute('datetime');
|
||||||
|
}
|
||||||
currentWeatherCondition = 'weather_unavailable';
|
currentWeatherCondition = 'weather_unavailable';
|
||||||
|
tomorrowWeatherCondition = 'weather_unavailable';
|
||||||
}
|
}
|
||||||
|
|
||||||
renderWeatherCondition();
|
renderWeatherCondition();
|
||||||
@@ -108,6 +188,16 @@ function setLanguage(lang) {
|
|||||||
document.getElementById('og-url').content = canonicalUrl;
|
document.getElementById('og-url').content = canonicalUrl;
|
||||||
document.getElementById('og-locale').content = languageLocales[lang];
|
document.getElementById('og-locale').content = languageLocales[lang];
|
||||||
renderWeatherCondition();
|
renderWeatherCondition();
|
||||||
|
|
||||||
|
const tomorrowDate = document.getElementById('weather-tomorrow-date');
|
||||||
|
if (tomorrowDate.dateTime) {
|
||||||
|
const [year, month, day] = tomorrowDate.dateTime.split('-').map(Number);
|
||||||
|
tomorrowDate.textContent = new Intl.DateTimeFormat(languageLocales[lang].replace('_', '-'), {
|
||||||
|
weekday: 'short',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
}).format(new Date(year, month - 1, day, 12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMobileMenu() {
|
function toggleMobileMenu() {
|
||||||
@@ -161,5 +251,5 @@ function closeModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLanguage(getRequestedLanguage() || getPreferredLanguage());
|
setLanguage(getRequestedLanguage() || getPreferredLanguage());
|
||||||
loadCurrentWeather();
|
loadWeatherForecast();
|
||||||
setInterval(loadCurrentWeather, 30 * 60 * 1000);
|
setInterval(loadWeatherForecast, 30 * 60 * 1000);
|
||||||
@@ -61,6 +61,10 @@ window.translations = {
|
|||||||
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
||||||
weather_title: "Thời điểm lý tưởng du lịch",
|
weather_title: "Thời điểm lý tưởng du lịch",
|
||||||
weather_desc: "Lên kế hoạch cho chuyến đi của bạn với thông tin thời tiết chính xác.",
|
weather_desc: "Lên kế hoạch cho chuyến đi của bạn với thông tin thời tiết chính xác.",
|
||||||
|
weather_current: "Thời tiết hiện tại",
|
||||||
|
weather_tomorrow: "Ngày mai",
|
||||||
|
weather_high: "Cao nhất",
|
||||||
|
weather_low: "Thấp nhất",
|
||||||
weather_loading: "Đang tải thời tiết...",
|
weather_loading: "Đang tải thời tiết...",
|
||||||
weather_unavailable: "Không có dữ liệu",
|
weather_unavailable: "Không có dữ liệu",
|
||||||
weather_clear: "Trời quang",
|
weather_clear: "Trời quang",
|
||||||
@@ -166,6 +170,10 @@ window.translations = {
|
|||||||
attr_9_loc: "Виньхи, Ниньхай",
|
attr_9_loc: "Виньхи, Ниньхай",
|
||||||
weather_title: "Лучшее время для поездки",
|
weather_title: "Лучшее время для поездки",
|
||||||
weather_desc: "Спланируйте свою поездку на основе точных метеорологических данных.",
|
weather_desc: "Спланируйте свою поездку на основе точных метеорологических данных.",
|
||||||
|
weather_current: "Сейчас",
|
||||||
|
weather_tomorrow: "Завтра",
|
||||||
|
weather_high: "Макс.",
|
||||||
|
weather_low: "Мин.",
|
||||||
weather_loading: "Загрузка погоды...",
|
weather_loading: "Загрузка погоды...",
|
||||||
weather_unavailable: "Нет данных",
|
weather_unavailable: "Нет данных",
|
||||||
weather_clear: "Ясно",
|
weather_clear: "Ясно",
|
||||||
@@ -271,6 +279,10 @@ window.translations = {
|
|||||||
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
||||||
weather_title: "Best Time to Visit",
|
weather_title: "Best Time to Visit",
|
||||||
weather_desc: "Plan your trip with accurate seasonal weather insights.",
|
weather_desc: "Plan your trip with accurate seasonal weather insights.",
|
||||||
|
weather_current: "Current weather",
|
||||||
|
weather_tomorrow: "Tomorrow",
|
||||||
|
weather_high: "High",
|
||||||
|
weather_low: "Low",
|
||||||
weather_loading: "Loading weather...",
|
weather_loading: "Loading weather...",
|
||||||
weather_unavailable: "Unavailable",
|
weather_unavailable: "Unavailable",
|
||||||
weather_clear: "Clear",
|
weather_clear: "Clear",
|
||||||
@@ -376,6 +388,10 @@ window.translations = {
|
|||||||
attr_9_loc: "宁海县永希",
|
attr_9_loc: "宁海县永希",
|
||||||
weather_title: "最佳旅游季节",
|
weather_title: "最佳旅游季节",
|
||||||
weather_desc: "根据精准气候指南,轻松规划您的完美行程。",
|
weather_desc: "根据精准气候指南,轻松规划您的完美行程。",
|
||||||
|
weather_current: "当前天气",
|
||||||
|
weather_tomorrow: "明天",
|
||||||
|
weather_high: "最高",
|
||||||
|
weather_low: "最低",
|
||||||
weather_loading: "正在加载天气...",
|
weather_loading: "正在加载天气...",
|
||||||
weather_unavailable: "暂无数据",
|
weather_unavailable: "暂无数据",
|
||||||
weather_clear: "晴朗",
|
weather_clear: "晴朗",
|
||||||
@@ -481,6 +497,10 @@ window.translations = {
|
|||||||
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
||||||
weather_title: "Beste Reisezeit",
|
weather_title: "Beste Reisezeit",
|
||||||
weather_desc: "Planen Sie Ihre Reise mit verlässlichen Informationen zum saisonalen Wetter.",
|
weather_desc: "Planen Sie Ihre Reise mit verlässlichen Informationen zum saisonalen Wetter.",
|
||||||
|
weather_current: "Aktuelles Wetter",
|
||||||
|
weather_tomorrow: "Morgen",
|
||||||
|
weather_high: "Höchstwert",
|
||||||
|
weather_low: "Tiefstwert",
|
||||||
weather_loading: "Wetter wird geladen...",
|
weather_loading: "Wetter wird geladen...",
|
||||||
weather_unavailable: "Keine Daten",
|
weather_unavailable: "Keine Daten",
|
||||||
weather_clear: "Klar",
|
weather_clear: "Klar",
|
||||||
@@ -586,6 +606,10 @@ window.translations = {
|
|||||||
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
attr_9_loc: "Vĩnh Hy, Ninh Hải",
|
||||||
weather_title: "Quand partir ?",
|
weather_title: "Quand partir ?",
|
||||||
weather_desc: "Planifiez votre séjour grâce à nos informations sur le climat selon les saisons.",
|
weather_desc: "Planifiez votre séjour grâce à nos informations sur le climat selon les saisons.",
|
||||||
|
weather_current: "Météo actuelle",
|
||||||
|
weather_tomorrow: "Demain",
|
||||||
|
weather_high: "Max.",
|
||||||
|
weather_low: "Min.",
|
||||||
weather_loading: "Chargement de la météo...",
|
weather_loading: "Chargement de la météo...",
|
||||||
weather_unavailable: "Données indisponibles",
|
weather_unavailable: "Données indisponibles",
|
||||||
weather_clear: "Ciel dégagé",
|
weather_clear: "Ciel dégagé",
|
||||||
|
|||||||
Reference in new issue
Block a user