add Meteo part
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -70,4 +70,5 @@ interface User {
|
||||
id: number,
|
||||
name: string,
|
||||
email: string,
|
||||
locations: {id: number, latitude: number, longitude: number}[],
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'utilisateur</h1>
|
||||
<div className="m-1 my-5 flex justify-between">
|
||||
<h1 className="text-lg font-bold">Profile de l'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>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from "react"
|
||||
import PageLayout from "../components/PageLayout"
|
||||
|
||||
const Meteo = () => {
|
||||
|
||||
return <PageLayout>
|
||||
Météo
|
||||
</PageLayout>
|
||||
}
|
||||
|
||||
export default Meteo
|
||||
@@ -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>
|
||||
|
||||
146
resources/js/pages/Weather.tsx
Normal file
146
resources/js/pages/Weather.tsx
Normal 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>
|
||||
}
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user