add Meteo part

This commit is contained in:
Romulus21
2023-09-24 11:19:58 +02:00
parent bfaf82f264
commit 33b2044859
23 changed files with 429 additions and 31 deletions

View File

@@ -3,14 +3,14 @@ import React, {
ReactElement
} from "react"
const Field: FC<FieldProps> = ({children, type = 'text', ...props}) => {
const Field: FC<FieldProps> = ({children, type = 'text', className = '', ...props}) => {
return <div className="form-control">
{children && <label className="block text-gray-900 dark:text-gray-200"
htmlFor={props.id ?? undefined}>
{children}
</label>}
<input className="w-full mt-2 rounded dark:bg-gray-700"
<input className={`${className} w-full mt-2 rounded dark:bg-gray-700`}
type={type}
{...props}/>
<div className={`error-message`} />
@@ -27,5 +27,7 @@ interface FieldProps {
value: any,
placeholder?: string,
autoFocus?: boolean,
className?: string,
step?: string,
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void,
}

View File

@@ -1,24 +1,25 @@
import React from "react"
import {Link} from "react-router-dom"
import {Link, useLocation} from "react-router-dom"
import useAuthUser from "../hooks/AuthUser"
const Header = () => {
const {authUser, logout} = useAuthUser()
const {authUser} = useAuthUser()
const location = useLocation()
return <header className="flex justify-between py-3 px-5 bg-blue-700 text-white text-xl">
<div>
<Link to="/">Bermite</Link>
</div>
{/*{authUser && <nav className="flex gap-2">*/}
{/* <Link to="/pluviometrie">Pluviométrie</Link>*/}
{/* <Link to="/meteo">Météo</Link>*/}
{/*</nav>}*/}
{authUser?.locations && authUser.locations.length > 0 && <nav className="flex gap-2">
<Link to="/pluviometrie" className={location.pathname === '/pluviometrie' ? 'font-bold' : ''}>Pluviométrie</Link>
<Link to="/meteo" className={location.pathname === '/meteo' ? 'font-bold' : ''}>Météo</Link>
</nav>}
{authUser
? <span className="flex gap-2">
<Link to="/profile">{authUser.name}</Link>
<Link to="/profile" className={location.pathname === '/profile' ? 'font-bold' : ''}>{authUser.name}</Link>
</span>
: <span className="flex gap-2">
<Link to="/connexion">Connexion</Link>

View File

@@ -1,7 +1,15 @@
const weekDays = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
interface Date {
getWeekDay(): string,
toSQLDate(): string,
}
Date.prototype.toSQLDate = function (): string {
return (new Date(this)).toISOString().split('T')[0]
}
Date.prototype.getWeekDay = function () {
const dayIndex = this.getDay() === 0 ? 6 : this.getDay() - 1
return weekDays[dayIndex]
}

View File

@@ -70,4 +70,5 @@ interface User {
id: number,
name: string,
email: string,
locations: {id: number, latitude: number, longitude: number}[],
}

View File

@@ -17,7 +17,7 @@ const useAxiosTools = () => {
if (error.response && error.response.status === 422) {
displayFormErrors(error)
} else {
setError(error.response.data.message || error.message)
setError(error.response?.data.message || error.message)
}
}

View File

@@ -1,25 +1,76 @@
import React from "react"
import React, {FormEvent, useState} from "react"
import useAuthUser from "../../hooks/AuthUser"
import PageLayout from "../../components/PageLayout"
import Card from "../../components/Card";
import Field from "../../components/Field";
import useAxiosTools from "../../hooks/AxiosTools";
const Profile = () => {
const {authUser, logout} = useAuthUser()
const {authUser, setAuthUser, logout} = useAuthUser()
const [latitude, setLatitude] = useState(0)
const [longitude, setLongitude] = useState(0)
const {errorCatch, axiosPost} = useAxiosTools()
const submitLocation = async (event: FormEvent) => {
event.preventDefault()
try {
const res = await axiosPost(`/api/locations`, {latitude, longitude})
setAuthUser(res.data)
} catch (e) {
errorCatch(e)
}
}
return <PageLayout>
<div className="flex justify-between">
<h1 className="mb-5 text-lg font-bold">Profile de l&apos;utilisateur</h1>
<div className="m-1 my-5 flex justify-between">
<h1 className="text-lg font-bold">Profile de l&apos;utilisateur</h1>
<div>
<button type="button" onClick={logout} className="btn-primary text-lg font-bold">Se déconnecter</button>
</div>
</div>
<div>
<Card className="mb-5">
<div>Nom: <strong>{authUser?.name}</strong></div>
<div>Email: <strong>{authUser?.email}</strong></div>
</div>
</Card>
{/*<div>Update name & email</div>*/}
{/*<div>Change password</div>*/}
{/*<div>Delete Account</div>*/}
<Card>
<h2>Météo</h2>
{authUser?.locations && authUser.locations.length > 0 ? <>
<h3>Emplacements</h3>
<ul>
{authUser?.locations.map(location => <li key={location.id}>{location.latitude} - {location.longitude}</li>)}
</ul>
</> : <form onSubmit={submitLocation}>
<h3>Ajouter un emplacement</h3>
<div className="flex gap-2">
<Field name="latitude"
type="number"
step="0.0001"
value={latitude}
className="h-10"
onChange={event => setLatitude(Number(event.target.value))}>
Latitude
</Field>
<Field name="longitude"
type="number"
step="0.0001"
value={longitude}
className="h-10"
onChange={event => setLongitude(Number(event.target.value))}>
Longitude
</Field>
<div className="self-end">
<button type="submit" className="btn-primary w-24 h-10">Valider</button>
</div>
</div>
</form>}
</Card>
</PageLayout>
}

View File

@@ -1,11 +0,0 @@
import React from "react"
import PageLayout from "../components/PageLayout"
const Meteo = () => {
return <PageLayout>
Météo
</PageLayout>
}
export default Meteo

View File

@@ -2,7 +2,7 @@ import React, {lazy, Suspense} from "react"
import {BrowserRouter, Route, Routes} from "react-router-dom"
import useAuthUser from "../hooks/AuthUser"
import Header from "../components/Header"
import Meteo from "./Meteo"
import Weather from "./Weather"
const ForgotPassword = lazy(() => import('./Auth/ForgotPassword'))
const Home = lazy(() => import('./Home'))
@@ -29,7 +29,7 @@ const Router = () => {
{/*<Route path="/sinscrire" element={<Register />} />*/}
<Route path="/mot-de-passe-oubliee" element={<ForgotPassword />} />
<Route path="/changer-le-mot-de-passe/:token" element={<Reset />} />
<Route path="/meteo" element={<Meteo />} />
<Route path="/meteo" element={<Weather />} />
<Route path="/pluviometrie" element={<Rainfall />} />
<Route path="/pluviometrie/mesures" element={<RainfallIndex />} />
</Routes>

View File

@@ -0,0 +1,146 @@
import React, {FC, useEffect, useState} from "react"
import PageLayout from "../components/PageLayout"
import useAxiosTools from "../hooks/AxiosTools";
import {WeatherValue} from "../types";
import Card from "../components/Card"
const Weather = () => {
const [currentWeather, setCurrentWeather] = useState<WeatherValue|null>(null)
const [weatherDays, setWeatherDays] = useState<[string, WeatherValue[]][]|null>(null)
const {errorLabel, errorCatch, cleanErrors, axiosGet} = useAxiosTools()
useEffect(() => {
(async () => fetchWeather())()
}, [])
const fetchWeather = async () => {
try {
cleanErrors()
const res = await axiosGet(`/api/weather`)
const currentWeather = res.data.list[0]
let weatherDays: [string, WeatherValue[]][] = []
let objectEntries = {index: -1, date: ''}
res.data.list.forEach((item: WeatherValue, index: number) => {
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)
} catch (e) {
errorCatch(e)
}
}
return <PageLayout>
{errorLabel()}
<Card className="flex justify-between">
<div className="flex flex-col m-2 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" />}
</div>
<div className="flex gap-1 flex-col">
<span className="text-4xl pt-5">{currentWeather?.main.temp_max.toFixed()} <span className="text-2xl">°C</span></span>
<span className="text-secondary text-2xl mt-2 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)
useEffect(() => {
const weatherState = {
min: 100,
max: -100,
main: '',
icon: '',
description: '',
}
const result: {[k: 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">
<div className="flex flex-col gap-2 flex-1 h-full">
<span className="font-bold text-lg" title={(new Date(date)).toLocaleDateString()}>{(new Date(date)).getWeekDay()}</span>
<span className="text-secondary dark:text-secondary-ligth">{weatherState?.description}</span>
</div>
<div className="flex items-center -mt-1.5">
<img src={`http://openweathermap.org/img/wn/${weatherState?.icon}@2x.png`}
className=""
alt={weatherState?.main + ' ' + weatherState?.icon} width="80px" />
</div>
<div className="flex gap-1 flex-col">
<span className="text-lg">{weatherState?.max.toFixed()} °C</span>
<span className="text-secondary dark:text-secondary-ligth">{weatherState?.min.toFixed()} °C</span>
</div>
</div>
}

View File

@@ -3,3 +3,32 @@ export interface rainfall {
date: string,
value: number,
}
export interface WeatherRequest {
list: WeatherValue[],
}
export interface WeatherValue {
dt_txt: string,
main: {
temp: number,
humidity: number,
pressure: number,
temp_max: number,
temp_min: number,
},
rain?: {
'3h': number,
},
weather: WeatherTime[],
}
export interface WeatherTime {
description: string,
icon: string,
main: 'Rain',
}
export interface WeatherCompilation {
[k: string]: WeatherValue[]
}