204 lines
8.0 KiB
TypeScript
204 lines
8.0 KiB
TypeScript
import React, {FC, Dispatch, SetStateAction, useEffect, useState} from "react"
|
|
import PageLayout from "../components/PageLayout"
|
|
import useAxiosTools from "../hooks/AxiosTools"
|
|
import {WeatherValue} from "../types"
|
|
import Card from "../components/Card"
|
|
import Img from "../components/Img"
|
|
|
|
const Weather = () => {
|
|
|
|
const [currentWeather, setCurrentWeather] = useState<WeatherValue | null>(null)
|
|
const [fetchTime, setFetchTime] = useState(0)
|
|
const [weatherDays, setWeatherDays] = useState<[string, WeatherValue[]][] | null>(null)
|
|
const {loading, setLoading, errorLabel, errorCatch, cleanErrors, axiosGet} = useAxiosTools()
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
if (!loading && (fetchTime + 5 * 60) < getCurrentTime()) {
|
|
fetchWeather()
|
|
}
|
|
}, 1000)
|
|
return () => clearInterval(timer)
|
|
}, [fetchTime])
|
|
|
|
const getCurrentTime = () => Number(((new Date).getTime() / 1000).toFixed())
|
|
|
|
const fetchWeather = async () => {
|
|
try {
|
|
setLoading(true)
|
|
cleanErrors()
|
|
const res = await axiosGet(`/api/weather`)
|
|
const currentWeather = res.data.list[0]
|
|
|
|
const weatherDays: [string, WeatherValue[]][] = []
|
|
let objectEntries = {index: -1, date: ''}
|
|
res.data.list.forEach((item: WeatherValue) => {
|
|
const date = item.dt_txt.split(' ')[0]
|
|
|
|
if (date === (new Date).toSQLDate()) {
|
|
if (currentWeather.main.temp_min > item.main.temp_min) {
|
|
currentWeather.main.temp_min = item.main.temp_min
|
|
}
|
|
if (currentWeather.main.temp_max < item.main.temp_max) {
|
|
currentWeather.main.temp_max = item.main.temp_max
|
|
}
|
|
}
|
|
|
|
if (date !== objectEntries.date) {
|
|
objectEntries = {index: objectEntries.index + 1, date}
|
|
}
|
|
|
|
if (!weatherDays[objectEntries.index]) {
|
|
weatherDays[objectEntries.index] = [date, []]
|
|
}
|
|
|
|
weatherDays[objectEntries.index][1] = [...weatherDays[objectEntries.index][1], item]
|
|
})
|
|
setWeatherDays(weatherDays)
|
|
setCurrentWeather(currentWeather)
|
|
setFetchTime(getCurrentTime())
|
|
} catch (e) {
|
|
errorCatch(e)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return <PageLayout>
|
|
|
|
{errorLabel()}
|
|
|
|
<Card className={`flex justify-between ${loading ? 'animate-pulse' : ''}`}>
|
|
<div className="m-2 flex flex-col justify-between">
|
|
<span className="text-6xl">{currentWeather?.main.temp.toFixed() ?? '--'} °C</span>
|
|
<span
|
|
className="text-secondary dark:text-secondary-ligth">{currentWeather?.weather[0].description}</span>
|
|
</div>
|
|
<div className="flex items-stretch">
|
|
<div>
|
|
{/*{currentWeather && <img src={`http://openweathermap.org/img/wn/${currentWeather?.weather[0].icon}@2x.png`}*/}
|
|
{/* alt={currentWeather?.weather[0].main} width="120px" />}*/}
|
|
{currentWeather && <Img src={`images/icons/${currentWeather?.weather[0].icon}.svg`}
|
|
alt={currentWeather?.weather[0].main} width="120px"/>}
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="pt-5 text-4xl">{currentWeather?.main.temp_max.toFixed() ?? '--'} <span
|
|
className="text-2xl">°C</span></span>
|
|
<span
|
|
className="mt-2 text-2xl text-secondary dark:text-secondary-ligth">{currentWeather?.main.temp_min.toFixed() ?? '--'} °C</span>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
<div className="m-3">
|
|
{weatherDays?.filter(([date]) => date !== (new Date).toSQLDate())
|
|
.map(([date, values]) => <WeatherCard key={date} date={date} values={values}/>)}
|
|
</div>
|
|
</PageLayout>
|
|
}
|
|
|
|
export default Weather
|
|
|
|
const WeatherCard: FC<{ date: string, values: WeatherValue[] }> = ({date, values = []}) => {
|
|
|
|
const [weatherState, setWeatherState] = useState<{
|
|
main: string,
|
|
description: string,
|
|
icon: string,
|
|
min: number,
|
|
max: number
|
|
} | null>(null)
|
|
const [showDetails, setShowDetails] = useState(false)
|
|
|
|
useEffect(() => {
|
|
console.log(values)
|
|
const weatherState = {
|
|
min: 100,
|
|
max: -100,
|
|
main: '',
|
|
icon: '',
|
|
description: '',
|
|
}
|
|
const result: Record<string, number> = {}
|
|
values.forEach(value => {
|
|
if (value.main.temp_min < weatherState.min) {
|
|
weatherState.min = value.main.temp_min
|
|
}
|
|
if (value.main.temp_max > weatherState.max) {
|
|
weatherState.max = value.main.temp_max
|
|
}
|
|
|
|
const tag = value.weather[0].main
|
|
if (!result[tag]) {
|
|
result[tag] = 0
|
|
}
|
|
result[tag]++
|
|
})
|
|
|
|
let itemToReturn: { name: string, value: number } | null = null
|
|
for (const item in result) {
|
|
if (!itemToReturn || itemToReturn.value < result[item]) {
|
|
itemToReturn = {name: item, value: result[item]}
|
|
}
|
|
}
|
|
|
|
if (itemToReturn && itemToReturn.name) {
|
|
const nameToSearch = itemToReturn.name
|
|
const value = values.find(item => item.weather[0].main === nameToSearch)
|
|
if (value) {
|
|
weatherState.main = itemToReturn.name
|
|
weatherState.description = value.weather[0].description
|
|
weatherState.icon = value.weather[0].icon.replace('n', 'd')
|
|
setWeatherState(weatherState)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
return <>
|
|
<div className="flex gap-5" onClick={(() => setShowDetails(!showDetails))}>
|
|
<div className="flex h-full flex-1 flex-col gap-2">
|
|
<span className="text-lg font-bold"
|
|
title={(new Date(date)).toLocaleDateString()}>{(new Date(date)).getWeekDay()}</span>
|
|
<span className="text-secondary dark:text-secondary-ligth">{weatherState?.description}</span>
|
|
</div>
|
|
<div className="-mt-1.5 flex items-center">
|
|
<Img src={`images/icons/${weatherState?.icon}.svg`}
|
|
alt={weatherState?.main + ' ' + weatherState?.icon}
|
|
width="80px"/>
|
|
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-lg">{weatherState?.max.toFixed()} °C</span>
|
|
<span className="text-secondary dark:text-secondary-ligth">{weatherState?.min.toFixed()} °C</span>
|
|
</div>
|
|
</div>
|
|
<WeatherDetails showDetails={showDetails} closeDetails={() => showDetails(false)} values={values}/>
|
|
</>
|
|
}
|
|
|
|
const WeatherDetails: FC<WeatherDetailsProps> = ({showDetails, closeDetails, values}) => {
|
|
|
|
return <ul onClick={closeDetails}
|
|
className={`${showDetails ? 'h-44 opacity-100' : 'h-0 opacity-0'} flex gap-2 overflow-hidden overflow-x-auto transition-all`}>
|
|
{values.map(value => <li key={value.dt} className="w-40">
|
|
<div className="text-center">{Number(value.dt_txt.split(' ')[1].split(':')[0])} h</div>
|
|
<div>
|
|
<Img src={`images/icons/${value.weather[0].icon.replace('n', 'd')}.svg`}
|
|
alt={value.weather[0].description}
|
|
width="80px"/>
|
|
</div>
|
|
<div className="text-center">
|
|
<span className="font-bold">
|
|
{value.main.temp}
|
|
</span> °C
|
|
</div>
|
|
{value.weather[0].description}
|
|
</li>)}
|
|
</ul>
|
|
}
|
|
|
|
interface WeatherDetailsProps {
|
|
showDetails: boolean,
|
|
closeDetails: Dispatch<SetStateAction<boolean>>,
|
|
values: WeatherValue[],
|
|
}
|