frontend/src/weather/weather.tsx

314 lines
10 KiB
TypeScript

import {
Box,
Grid2 as Grid,
LinearProgress,
Typography,
} from "@mui/material";
import { useState } from "react";
import OpenWeatherMap from "openweathermap-ts";
import { CurrentResponse, ThreeHourResponse } from "openweathermap-ts/dist/types";
import { useEffect } from "react";
import WeatherIcon from "./weatherIcon";
import { getThemeType } from "theme";
import { useMobile, useThemeType } from "hooks";
import WindSpeedDark from "assets/components/windSpeedDark.png";
import WindSpeedLight from "assets/components/windSpeedLight.png";
import { makeStyles } from "@mui/styles";
//import { WiHumidity } from "weather-icons-react";
const openWeather = new OpenWeatherMap({
//apiKey: process.env.REACT_APP_OPEN_WEATHERMAP ? process.env.REACT_APP_OPEN_WEATHERMAP : ""
apiKey: import.meta.env.VITE_OPEN_WEATHERMAP ? import.meta.env.VITE_OPEN_WEATHERMAP : ""
});
interface Props {
longitude?: number;
latitude?: number;
}
const useStyles = makeStyles(() => ({
currentBox: {
boxShadow: "0px 0px 10px black",
height: "110px",
borderRadius: 10,
margin: 7
},
upperSquare: {
height: "75%",
display: "flex",
justifyContent: "center",
alignItems: "center"
},
lowerSquare: {
height: "25%",
display: "flex",
justifyContent: "center",
alignItems: "center"
},
dark: {
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)",
padding: 5,
opacity: 1
},
light: {
backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)",
padding: 5,
opacity: 1
}
}))
export default function Weather(props: Props) {
const [currentConditions, setCurrentConditions] = useState<CurrentResponse>();
const [forecast, setForecast] = useState<ThreeHourResponse>();
const classes = useStyles();
const isMobile = useMobile();
const themeType = useThemeType();
useEffect(() => {
if (props.longitude && props.latitude) {
openWeather
.getThreeHourForecastByGeoCoordinates(props.latitude, props.longitude)
.then(forecast => {
setForecast(forecast);
});
openWeather
.getCurrentWeatherByGeoCoordinates(props.latitude, props.longitude)
.then(weather => {
setCurrentConditions(weather);
});
}
}, [props.latitude, props.longitude]);
const celsiusConverter = (temp: number) => {
return (temp - 32) * (5 / 9);
};
/**
* gets the high and low of the first 8 readings in the 5 day forecast, is technically getting the high and low for tomorrow
* but is usually close to the same temperatures, for actual current high/low must use a paid plan for open weather
* @returns
*/
const getHighLow = () => {
if (!forecast) return;
let loopCounter = 0;
let currentHigh = -1000;
let currentLow = 1000;
while (loopCounter <= 7) {
let temp = forecast.list[loopCounter].main.temp;
if (temp < currentLow) {
currentLow = temp;
}
if (temp > currentHigh) {
currentHigh = temp;
}
loopCounter++;
}
return (
<Typography variant="h5" style={{ fontSize: 25 }}>
{Math.round(celsiusConverter(currentHigh))}&#176; /{" "}
{Math.round(celsiusConverter(currentLow))}&#176;
</Typography>
);
};
const getDirection = (current: CurrentResponse) => {
if (!current) return;
let windDegrees = current.wind.deg;
let direction = "";
if (windDegrees > 338 || windDegrees <= 23) {
direction = "N";
}
if (windDegrees > 23 && windDegrees <= 67) {
direction = "NE";
}
if (windDegrees > 67 && windDegrees <= 112) {
direction = "E";
}
if (windDegrees > 112 && windDegrees <= 158) {
direction = "SE";
}
if (windDegrees > 158 && windDegrees <= 203) {
direction = "S";
}
if (windDegrees > 203 && windDegrees <= 248) {
direction = "SW";
}
if (windDegrees > 248 && windDegrees <= 293) {
direction = "W";
}
if (windDegrees > 293 && windDegrees <= 338) {
direction = "NW";
}
return direction;
};
const dayOfWeek = (day: number) => {
switch (day) {
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
default:
return "Sunday";
}
};
/**
* Three hour response contains a list of 40 items for the weather, each 8 is a 24 hour period
* loop through 8 items to get the forecast for that 24 hour period
* @param forecast the three hour response that was retrieved
* @param day the day we are getting the forecast for as a number
*/
const getForeCast = (day: number) => {
if (!forecast) return;
let startingPoint = day * 8;
let loopCount = 0;
let high = -1000;
let low = 1000;
let d = forecast.list[startingPoint + 4].dt;
let date = new Date(0);
date.setUTCSeconds(d);
while (loopCount < 8) {
let temp = forecast.list[startingPoint].main.temp;
if (temp > high) {
high = temp;
}
if (temp < low) {
low = temp;
}
startingPoint++;
loopCount++;
}
return (
<Box className={day % 2 === 0 ? classes.dark : classes.light}>
<Box style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1" style={{ fontWeight: 600 }}>
{dayOfWeek(date.getDay())}
</Typography>
<Typography variant="subtitle1" style={{ fontWeight: 600 }}>
{Math.round(celsiusConverter(high))}&#176; / {Math.round(celsiusConverter(low))}&#176;
</Typography>
</Box>
</Box>
);
};
return (
<Box padding="25px">
{currentConditions && forecast ? (
<Box>
<Box component="span">
<Typography style={{ fontSize: 20, fontWeight: 750 }}>
Current Weather - {dayOfWeek(new Date().getDay())}
</Typography>
</Box>
<Grid container direction="row">
<Grid size={6}>
<Box className={classes.currentBox}>
<Box
padding="5%"
className={classes.upperSquare}
style={{ flexDirection: isMobile ? "column" : "row" }}>
<WeatherIcon
openweatherIconId={currentConditions.weather[0].id}
size={isMobile ? 35 : 50}
/>
<Typography variant="body1" style={{ fontSize: 25 }}>
{Math.round(celsiusConverter(currentConditions.main.temp))}&#8451;
</Typography>
</Box>
<Box className={classes.lowerSquare}>
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
Feels like {Math.round(celsiusConverter(currentConditions.main.feels_like))}
&#8451;
</Typography>
</Box>
</Box>
</Grid>
<Grid size={6}>
<Box className={classes.currentBox}>
<Box
className={classes.upperSquare}
style={{ flexDirection: isMobile ? "column" : "row" }}>
{/* <WiHumidity size={isMobile ? 35 : 50} /> */}
<Typography variant="body1" style={{ fontSize: 25, display: "inline" }}>
{currentConditions.main.humidity}%
</Typography>
</Box>
<Box className={classes.lowerSquare}>
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
Humidity
</Typography>
</Box>
</Box>
</Grid>
<Grid size={6}>
<Box className={classes.currentBox}>
<Box
className={classes.upperSquare}
style={{ flexDirection: isMobile ? "column" : "row" }}>
<img
src={themeType === "light" ? WindSpeedDark : WindSpeedLight}
alt="Wind Speed"
height={isMobile ? 35 : 50}
width={isMobile ? 35 : 50}
/>
<Box>
<Typography variant="body1" style={{ fontSize: 25, display: "inline" }}>
{Math.round(currentConditions.wind.speed * 3.6)}/
</Typography>
<Typography variant="subtitle1" style={{ fontSize: 15, display: "inline" }}>
km/h
</Typography>
</Box>
</Box>
<Box className={classes.lowerSquare}>
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
Direction {getDirection(currentConditions)}
</Typography>
</Box>
</Box>
</Grid>
<Grid size={6}>
<Box className={classes.currentBox}>
<Box className={classes.upperSquare} style={{ paddingTop: isMobile ? 35 : 0 }}>
{getHighLow()}
</Box>
<Box className={classes.lowerSquare}>
<Typography variant="subtitle1" style={{ fontSize: isMobile ? 10 : 15 }}>
Today's High/Low
</Typography>
</Box>
</Box>
</Grid>
</Grid>
<Box component="span">
<Typography style={{ fontSize: 20, fontWeight: 750 }}>5 - Day Forecast</Typography>
</Box>
<Box>
{getForeCast(0)}
{getForeCast(1)}
{getForeCast(2)}
{getForeCast(3)}
{getForeCast(4)}
</Box>
</Box>
) : (
<Box paddingTop="20px">
<LinearProgress color="primary" />
</Box>
)}
</Box>
);
}