324 lines
8.4 KiB
JavaScript
324 lines
8.4 KiB
JavaScript
"use client";
|
||
|
||
import { getWeatherIcon } from "@/utils/weather.js";
|
||
import Image from "next/image";
|
||
import { useEffect, useState } from "react";
|
||
import List from "./daily.json";
|
||
import styles from "./todo.module.css";
|
||
|
||
const version = require("../../../package.json").version;
|
||
|
||
export default function WeatherPage() {
|
||
const [weather, setWeather] = useState(null);
|
||
const [time, setTime] = useState(new Date().toLocaleTimeString());
|
||
const [date, setDate] = useState(new Date().toLocaleDateString());
|
||
const [weatherIcon, setWeatherIcon] = useState("/weather/sunnyday.png");
|
||
|
||
useEffect(() => {
|
||
async function fetchWeather() {
|
||
try {
|
||
const res = await fetch(
|
||
`https://api.pirateweather.net/forecast/${process.env.NEXT_PUBLIC_WEATHER_API}/42.3967,-88.1837?units=us&exclude=minutely,hourly,flags&icon=pirate`,
|
||
{ cache: "no-store" },
|
||
);
|
||
const data = await res.json();
|
||
setWeather(data);
|
||
} catch (err) {
|
||
console.error("Failed to fetch weather:", err);
|
||
}
|
||
}
|
||
|
||
// only fetch weather every 5 minutes on the 0th second
|
||
const fetchWeatherInterval = () => {
|
||
const now = new Date();
|
||
|
||
if (now.getSeconds() === 0 && now.getMinutes() % 5 === 0) {
|
||
fetchWeather();
|
||
}
|
||
};
|
||
|
||
fetchWeather();
|
||
|
||
const interval = setInterval(fetchWeatherInterval, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
setTime(new Date().toLocaleTimeString());
|
||
}, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
setDate(new Date().toLocaleDateString());
|
||
}, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
const weatherTemp = Math.floor(weather?.currently?.temperature) || "--";
|
||
|
||
const weatherFeelsLike =
|
||
Math.floor(weather?.currently?.apparentTemperature) || "--";
|
||
|
||
const weatherHigh =
|
||
Math.floor(weather?.daily?.data[0]?.temperatureHigh) || "--";
|
||
const weatherLow =
|
||
Math.floor(weather?.daily?.data[0]?.temperatureLow) || "--";
|
||
|
||
const percentChance =
|
||
Math.floor(weather?.currently?.precipProbability * 100) || "0";
|
||
|
||
const lastUpdatedTime = weather
|
||
? new Date(weather.currently.time * 1000).toLocaleTimeString([], {
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
})
|
||
: "--";
|
||
|
||
useEffect(() => {
|
||
const interval = setInterval(() => {
|
||
setWeatherIcon(
|
||
`/weather/${getWeatherIcon(weather?.currently?.icon)}.png`,
|
||
);
|
||
}, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, [weather]);
|
||
|
||
const [dailyItems, setDailyItems] = useState([]);
|
||
|
||
useEffect(() => {
|
||
const updateDailyItems = () => {
|
||
const now = new Date();
|
||
const currentHour = now.getHours();
|
||
|
||
const filteredItems =
|
||
List?.List?.filter((item) => {
|
||
const timeStart = parseInt(item.timeStart);
|
||
const timeEnd = parseInt(item.timeEnd);
|
||
|
||
return currentHour >= timeStart && currentHour < timeEnd;
|
||
}) || [];
|
||
|
||
setDailyItems(
|
||
filteredItems.length > 0
|
||
? filteredItems
|
||
: [
|
||
{
|
||
title: "N/A",
|
||
desc: "No reminders",
|
||
timeStart: "--",
|
||
timeEnd: "--",
|
||
},
|
||
],
|
||
);
|
||
};
|
||
|
||
// Initial check
|
||
updateDailyItems();
|
||
|
||
// Check every second
|
||
const interval = setInterval(updateDailyItems, 1000);
|
||
|
||
return () => clearInterval(interval);
|
||
}, [List]);
|
||
|
||
const weatherAlerts = weather?.alerts || [];
|
||
|
||
const futureForecasts = weather?.daily?.data.slice(1, 4) || [];
|
||
|
||
return (
|
||
<div className={styles.todoContainer}>
|
||
<div className={styles.todoHeader}>
|
||
<span className={styles.left}>
|
||
<span className={styles.title}>Raceway Car Wash</span>
|
||
<span className={styles.location}>
|
||
Fox Lake{" "}
|
||
<span className={styles.version}>v{version}</span>
|
||
</span>
|
||
</span>
|
||
<span className={styles.right}>
|
||
<span className={styles.time}>{time || "00:00:00 AM"}</span>
|
||
<span className={styles.date}>{date}</span>
|
||
</span>
|
||
</div>
|
||
<span className={styles.container}>
|
||
<span className={styles.listContainer}>
|
||
<span className={styles.lists}>
|
||
<span className={styles.listHeader}>
|
||
Daily Reminders
|
||
</span>
|
||
{dailyItems?.map((item, index) => (
|
||
<span key={index} className={styles.dailyListItem}>
|
||
<span className={styles.listItemTitle}>
|
||
{item.title}
|
||
</span>
|
||
<span className={styles.listItemDesc}>
|
||
{item.desc}
|
||
</span>
|
||
<span className={styles.listItemTime}>
|
||
{item.textStart && item.textEnd ? (
|
||
<>
|
||
{item.textStart} - {item.textEnd}
|
||
</>
|
||
) : (
|
||
<>
|
||
{item.timeStart}:00 - {item.timeEnd}
|
||
:00
|
||
</>
|
||
)}
|
||
</span>
|
||
</span>
|
||
)) || "Test"}
|
||
</span>
|
||
<span className={styles.lists}>
|
||
<span className={styles.listHeader}>
|
||
Important Reminders
|
||
</span>
|
||
<span className={styles.dailyListItem}>
|
||
<span className={styles.listItemTitle}>N/A</span>
|
||
<span className={styles.listItemDesc}>
|
||
No Reminders
|
||
</span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherContainer}>
|
||
<span className={styles.weatherContainerInner}>
|
||
<span className={styles.weatherHeader}>
|
||
⛅ Hyperlocal Weather
|
||
</span>
|
||
|
||
{weatherAlerts.length > 0 &&
|
||
weatherAlerts.map((alert, index) => (
|
||
<span
|
||
key={index}
|
||
className={styles.weatherAlerts}
|
||
>
|
||
<span className={styles.weatherAlert}>
|
||
⚠️ {alert.title}
|
||
<span
|
||
className={
|
||
styles.weatherAlertExpires
|
||
}
|
||
>
|
||
Expires:{" "}
|
||
{new Date(
|
||
alert.expires * 1000,
|
||
).toLocaleString()}
|
||
</span>
|
||
</span>
|
||
</span>
|
||
))}
|
||
|
||
<span className={styles.weatherTop}>
|
||
<span className={styles.weatherTopLeft}>
|
||
<span className={styles.weatherCondition}>
|
||
{weather?.currently?.summary}
|
||
</span>
|
||
<span className={styles.weatherTemp}>
|
||
{weatherTemp}°
|
||
</span>
|
||
<span className={styles.weatherFeelsLike}>
|
||
Feels Like {weatherFeelsLike}°
|
||
</span>
|
||
<span className={styles.weatherHighLow}>
|
||
High: {weatherHigh}° · Low:{" "}
|
||
{weatherLow}°
|
||
</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherTopRight}>
|
||
<Image
|
||
alt="Weather Icon"
|
||
src={weatherIcon}
|
||
width={175}
|
||
height={175}
|
||
loading="eager"
|
||
/>
|
||
</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherDetails}>
|
||
<span className={styles.detail}>
|
||
{weather?.currently.windSpeed || "--"}
|
||
<span className={styles.detailUnit}>mph</span>
|
||
<span className={styles.detailText}>
|
||
Wind Speed
|
||
</span>
|
||
</span>
|
||
<span className={styles.detail}>
|
||
{percentChance}%
|
||
<span className={styles.detailText}>
|
||
Precipitation
|
||
</span>
|
||
</span>
|
||
<span className={styles.detail}>
|
||
{Math.floor(
|
||
weather?.currently?.humidity * 100,
|
||
) || "--"}
|
||
%
|
||
<span className={styles.detailText}>
|
||
Humidity
|
||
</span>
|
||
</span>
|
||
</span>
|
||
|
||
<span className={styles.weatherFutureForecasts}>
|
||
{futureForecasts.map((forecast, index) => (
|
||
<span
|
||
key={index}
|
||
className={styles.futureForecast}
|
||
>
|
||
<span className={styles.futureForecastDay}>
|
||
{new Date(
|
||
forecast.time * 1000,
|
||
).toLocaleDateString([], {
|
||
weekday: "short",
|
||
})}
|
||
</span>
|
||
<span
|
||
className={
|
||
styles.futureForecastIconContainer
|
||
}
|
||
>
|
||
<Image
|
||
alt="Weather Icon"
|
||
src={`/weather/${getWeatherIcon(forecast.icon)}.png`}
|
||
className={
|
||
styles.futureForecastIcon
|
||
}
|
||
width={40}
|
||
height={40}
|
||
unoptimized
|
||
/>
|
||
</span>
|
||
<span className={styles.futurePrecipChance}>
|
||
{Math.floor(
|
||
forecast.precipProbability * 100,
|
||
)}
|
||
%
|
||
</span>
|
||
<span className={styles.futureForecastTemp}>
|
||
{Math.floor(forecast.temperatureHigh)}°
|
||
/ {Math.floor(forecast.temperatureLow)}°
|
||
</span>
|
||
</span>
|
||
))}
|
||
</span>
|
||
|
||
<span className={styles.weatherFooter}>
|
||
Last Updated {lastUpdatedTime}
|
||
</span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|