first commt
This commit is contained in:
3
resources/css/app.css
Normal file
3
resources/css/app.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
17
resources/js/app.tsx
Normal file
17
resources/js/app.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
//import './bootstrap';
|
||||
import '../css/app.css';
|
||||
import App from "./pages/App";
|
||||
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import React from 'react';
|
||||
import './customPrototypes'
|
||||
|
||||
|
||||
//const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||
|
||||
const container = document.getElementById('app')
|
||||
if (container) {
|
||||
const root = createRoot(container)
|
||||
root.render(<App />)
|
||||
}
|
||||
|
||||
32
resources/js/bootstrap.js
vendored
Normal file
32
resources/js/bootstrap.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
30
resources/js/components/Field.tsx
Normal file
30
resources/js/components/Field.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, {
|
||||
FC, HTMLInputTypeAttribute,
|
||||
ReactElement
|
||||
} from "react"
|
||||
|
||||
const Field: FC<FieldProps> = ({children, type = 'text', ...props}) => {
|
||||
|
||||
return <div className="form-group">
|
||||
{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"
|
||||
type={type}
|
||||
{...props}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Field
|
||||
|
||||
interface FieldProps {
|
||||
children?: ReactElement|string,
|
||||
type?: HTMLInputTypeAttribute,
|
||||
name: string,
|
||||
id?: string,
|
||||
value: any,
|
||||
placeholder?: string,
|
||||
autoFocus?: boolean,
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void,
|
||||
}
|
||||
27
resources/js/components/Header.tsx
Normal file
27
resources/js/components/Header.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import useAuthUser from "../hooks/AuthUser";
|
||||
|
||||
const Header = () => {
|
||||
|
||||
const {authUser, logout} = useAuthUser()
|
||||
|
||||
return <header className="bg-blue-700 text-white py-3 px-5 text-xl flex justify-between">
|
||||
<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 ? <span className="flex gap-2"><Link to="/profile">{authUser.name}</Link><button onClick={logout}>logout</button></span>
|
||||
: <span className="flex gap-2">
|
||||
<Link to="/connexion">Connexion</Link>
|
||||
<Link to="/sinscrire">S'inscrire</Link>
|
||||
</span>}
|
||||
</header>
|
||||
}
|
||||
|
||||
export default Header
|
||||
10
resources/js/components/PageLayout.tsx
Normal file
10
resources/js/components/PageLayout.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React, {PropsWithChildren} from "react"
|
||||
|
||||
const PageLayout = ({children}: PropsWithChildren) => {
|
||||
|
||||
return <div className="m-2">
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default PageLayout
|
||||
45
resources/js/components/rainfall/AddRainfall.tsx
Normal file
45
resources/js/components/rainfall/AddRainfall.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, {Dispatch, FC, FormEvent, SetStateAction, useState} from "react"
|
||||
import useAxiosTools from "../../hooks/AxiosTools";
|
||||
import Field from "../Field";
|
||||
|
||||
const AddRainfall: FC<AddRainfallProps> = ({reload}) => {
|
||||
|
||||
const {axiosPost} = useAxiosTools()
|
||||
const [date, setDate] = useState((new Date()).toSQLDate())
|
||||
const [value, setValue] = useState(0)
|
||||
|
||||
const handleSubmit = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
try {
|
||||
await axiosPost('/api/rainfalls', {date, value})
|
||||
setDate((new Date()).toSQLDate())
|
||||
setValue(0)
|
||||
reload(new Date())
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<h2>Ajout d'une mesure</h2>
|
||||
<Field type="date"
|
||||
name="date"
|
||||
placeholder="Email"
|
||||
value={date}
|
||||
onChange={event => setDate(event.target.value)}
|
||||
autoFocus>Date</Field>
|
||||
<Field type="number"
|
||||
name="value"
|
||||
placeholder="10"
|
||||
value={value}
|
||||
onChange={event => setValue(Number(event.target.value))}>Mesure</Field>
|
||||
|
||||
<button type="submit" className="mt-3 w-full bg-blue-700 rounded">Valider</button>
|
||||
</form>
|
||||
}
|
||||
|
||||
export default AddRainfall
|
||||
|
||||
interface AddRainfallProps {
|
||||
reload: Dispatch<SetStateAction<Date>>
|
||||
}
|
||||
44
resources/js/components/rainfall/LastFiveMesure.tsx
Normal file
44
resources/js/components/rainfall/LastFiveMesure.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, {FC, useEffect, useState} from "react"
|
||||
import useAxiosTools from "../../hooks/AxiosTools";
|
||||
import {rainfall} from "../../types";
|
||||
import {AxiosError} from "axios";
|
||||
|
||||
const LastFiveMesure: FC<LastFiveMesureProps> = ({loadedAt}) => {
|
||||
|
||||
const {error, setError, axiosGet} = useAxiosTools()
|
||||
const [data, setData] = useState<rainfall[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [loadedAt])
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await axiosGet('/api/rainfalls/last')
|
||||
setData(res.data)
|
||||
} catch (e) {
|
||||
if (e instanceof AxiosError) {
|
||||
setError(e.message)
|
||||
} else {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <div>
|
||||
<h1>5 dernières mesures</h1>
|
||||
{error && <div>{error}</div>}
|
||||
<ul>
|
||||
{data.map(line => <li key={line.id} className="w-36 flex justify-between">
|
||||
<span>{(new Date(line.date)).toLocaleDateString()}</span>
|
||||
<span>{line.value}</span>
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default LastFiveMesure
|
||||
|
||||
interface LastFiveMesureProps {
|
||||
loadedAt: Date,
|
||||
}
|
||||
100
resources/js/components/rainfall/RainfallGraph.tsx
Normal file
100
resources/js/components/rainfall/RainfallGraph.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import * as d3 from "d3"
|
||||
import React, {FC, LegacyRef, useEffect, useRef} from "react"
|
||||
import {rainfall} from "../../types";
|
||||
|
||||
const RainfallGraph: FC<RainfallGraphProps> = ({width, height, data, start_date, end_date}) => {
|
||||
|
||||
const svgRef = useRef<SVGSVGElement|null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
renderSVG()
|
||||
}, [width, height, data])
|
||||
|
||||
const renderSVG = () => {
|
||||
const margin = {top: 10, right: 30, bottom: 30, left: 20}
|
||||
const gWidth = width - margin.left - margin.right
|
||||
const gHeight = height - margin.top - margin.bottom
|
||||
console.log('data', data)
|
||||
|
||||
d3.select(svgRef.current).selectAll("*").remove()
|
||||
const svg = d3.select(svgRef.current)
|
||||
.attr('width', gWidth + margin.left + margin.right)
|
||||
.attr('height', gHeight + margin.top + margin.bottom)
|
||||
.attr('class', 'relative')
|
||||
.append('g')
|
||||
.attr('transform', "translate(" + margin.left + "," + margin.top + ")")
|
||||
|
||||
const yMax = data.reduce((result, item) => item.value > result ? item.value : result, 0)
|
||||
const y = d3.scaleLinear()
|
||||
.domain([0, yMax + 10])
|
||||
.range([gHeight, 0])
|
||||
|
||||
const x = d3.scaleUtc()
|
||||
.domain([(new Date(start_date)), (new Date(end_date))])
|
||||
.range([margin.left, width - margin.right])
|
||||
|
||||
const yAxis = svg.append('g')
|
||||
.attr('transform', `translate(${margin.left},0)`)
|
||||
.call(d3.axisLeft(y).ticks(height / 80))
|
||||
.call(g => g.select('.domain').remove())
|
||||
|
||||
.call(g => g.append('text')
|
||||
.attr('x', -margin.left)
|
||||
.attr('y', 10)
|
||||
.attr('fill', 'currentColor')
|
||||
.attr('text-anchor', 'start')
|
||||
)
|
||||
|
||||
svg.append("g")
|
||||
.attr("transform", "translate(0," + gHeight + ")")
|
||||
.call(d3.axisBottom(x)
|
||||
.tickFormat(
|
||||
// @ts-ignore
|
||||
d3.timeFormat("%d/%m/%Y")
|
||||
)
|
||||
, 0)
|
||||
|
||||
if (data.length === 0) {
|
||||
// no values text
|
||||
const titleBox = svg.append("g")
|
||||
.attr("x", (width))
|
||||
.attr("y", height)
|
||||
|
||||
titleBox.append("text")
|
||||
.attr("x", (width / 2))
|
||||
.attr("y", height / 2)
|
||||
.attr("text-anchor", "middle")
|
||||
.style("font-size", "20px")
|
||||
.text('Aucune Donnée')
|
||||
} else {
|
||||
yAxis.call(g => g.selectAll(".tick line").clone()
|
||||
.attr("x2", width - margin.left - margin.right)
|
||||
.attr("stroke-opacity", 0.1))
|
||||
}
|
||||
|
||||
svg.selectAll(".bar")
|
||||
.data(data)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("class", "bar")
|
||||
.attr("fill", "steelblue")
|
||||
.attr("x", d => x(new Date(d.date)) * (1 - (100 / data.length)/100))
|
||||
.attr("y", d => y(d.value))
|
||||
.attr("width", (width - 44 + ((width - 44) / (data.length + 1))) / (data.length + 1) - 2)
|
||||
.attr("height", d => height - margin.bottom - 10 - y(d.value))
|
||||
.append('title')
|
||||
.text(d => `${(new Date(d.date)).toLocaleDateString()} : ${d.value}`)
|
||||
}
|
||||
|
||||
return <svg ref={svgRef} />
|
||||
}
|
||||
|
||||
export default RainfallGraph
|
||||
|
||||
interface RainfallGraphProps {
|
||||
width: number,
|
||||
height: number,
|
||||
data: rainfall[],
|
||||
start_date: string,
|
||||
end_date: string,
|
||||
}
|
||||
7
resources/js/customPrototypes.ts
Normal file
7
resources/js/customPrototypes.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
interface Date {
|
||||
toSQLDate(): string,
|
||||
}
|
||||
|
||||
Date.prototype.toSQLDate = function (): string {
|
||||
return (new Date(this)).toISOString().split('T')[0]
|
||||
}
|
||||
73
resources/js/hooks/AuthUser.tsx
Normal file
73
resources/js/hooks/AuthUser.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React, {
|
||||
createContext,
|
||||
Dispatch,
|
||||
PropsWithChildren,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState
|
||||
} from "react";
|
||||
import axios from "axios";
|
||||
|
||||
const AuthUserContext = createContext<AuthUserProps|undefined>(undefined)
|
||||
|
||||
interface AuthUserProps {
|
||||
authUser?: User|null,
|
||||
setAuthUser: Dispatch<SetStateAction<User | null>>,
|
||||
loadingAuthUser: boolean,
|
||||
logout: () => void,
|
||||
}
|
||||
|
||||
export const AuthUserProvider = ({children}: PropsWithChildren) => {
|
||||
const [authUser, setAuthUser] = useState<User|null>(null)
|
||||
const [loadingAuthUser, setLoadingAuthUser] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const res = await axios.get('/api/user')
|
||||
setAuthUser(res.data)
|
||||
} catch (e) {
|
||||
// @ts-ignore
|
||||
if (e.response.status === 401) {
|
||||
console.info('no user login')
|
||||
}
|
||||
} finally {
|
||||
setLoadingAuthUser(false)
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
setLoadingAuthUser(false)
|
||||
const res = await axios.delete('/api/logout')
|
||||
setAuthUser(null)
|
||||
window.location.replace('/')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
setLoadingAuthUser(false)
|
||||
}
|
||||
}
|
||||
|
||||
return <AuthUserContext.Provider value={{authUser, setAuthUser, loadingAuthUser, logout}}>
|
||||
{children}
|
||||
</AuthUserContext.Provider>
|
||||
}
|
||||
|
||||
const useAuthUser = () => {
|
||||
const context = useContext(AuthUserContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('Add AuthUserProvider to use AuthUserContext')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export default useAuthUser
|
||||
|
||||
interface User {
|
||||
id: number,
|
||||
name: string,
|
||||
email: string,
|
||||
}
|
||||
15
resources/js/hooks/AxiosTools.tsx
Normal file
15
resources/js/hooks/AxiosTools.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import {useState} from "react";
|
||||
import axios from "axios";
|
||||
|
||||
const useAxiosTools = () => {
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string|null>(null)
|
||||
|
||||
const axiosGet = axios.get
|
||||
const axiosPost = axios.post
|
||||
|
||||
return {loading, setLoading, error, setError, axiosGet, axiosPost}
|
||||
}
|
||||
|
||||
export default useAxiosTools
|
||||
36
resources/js/hooks/DimensionHook.tsx
Normal file
36
resources/js/hooks/DimensionHook.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import {useEffect, useLayoutEffect, useRef, useState} from "react"
|
||||
|
||||
const useDimension = () => {
|
||||
|
||||
const RESET_TIMEOUT = 300
|
||||
let movement_timer: number|undefined = undefined
|
||||
const targetRef = useRef<any>()
|
||||
const [dimensions, setDimensions] = useState({ width:0, height: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', ()=>{
|
||||
clearInterval(movement_timer)
|
||||
movement_timer = setTimeout(testDimensions, RESET_TIMEOUT)
|
||||
})
|
||||
})
|
||||
|
||||
useLayoutEffect(() => {
|
||||
testDimensions()
|
||||
}, [])
|
||||
|
||||
const testDimensions = () => {
|
||||
if (targetRef.current) {
|
||||
setDimensions({
|
||||
width: targetRef.current.offsetWidth,
|
||||
height: targetRef.current.offsetHeight
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
targetRef,
|
||||
dimensions,
|
||||
}
|
||||
}
|
||||
|
||||
export default useDimension
|
||||
14
resources/js/pages/App.tsx
Normal file
14
resources/js/pages/App.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react"
|
||||
import {AuthUserProvider} from "../hooks/AuthUser";
|
||||
import Router from "./Router";
|
||||
|
||||
const App = () => {
|
||||
|
||||
return <main className="dark:bg-gray-900 dark:text-white h-screen">
|
||||
<AuthUserProvider>
|
||||
<Router />
|
||||
</AuthUserProvider>
|
||||
</main>
|
||||
}
|
||||
|
||||
export default App
|
||||
46
resources/js/pages/Auth/Login.tsx
Normal file
46
resources/js/pages/Auth/Login.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, {FormEvent, SyntheticEvent, useState} from "react"
|
||||
import Field from "../../components/Field";
|
||||
import axios from "axios";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import useAuthUser from "../../hooks/AuthUser";
|
||||
|
||||
const Login = () => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const {setAuthUser} = useAuthUser()
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const handleSubmit = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
try {
|
||||
await axios.get('/sanctum/csrf-cookie')
|
||||
const res = await axios.post('/api/login', {email, password})
|
||||
setAuthUser(res.data.user)
|
||||
navigate('/')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return <div>
|
||||
<form onSubmit={handleSubmit} className="w-96 mx-auto mt-10 border shadow p-3">
|
||||
<h1 className="text-center">Connexion</h1>
|
||||
|
||||
<Field type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={event => setEmail(event.target.value)}
|
||||
autoFocus>Email</Field>
|
||||
<Field type="password"
|
||||
name="password"
|
||||
placeholder="******"
|
||||
value={password}
|
||||
onChange={event => setPassword(event.target.value)}>Mot de passe</Field>
|
||||
<button type="submit" className="mt-5 bg-blue-700 w-full block text-white px-5 py-2 text-lg rounded">Valider</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Login
|
||||
19
resources/js/pages/Auth/Profile.tsx
Normal file
19
resources/js/pages/Auth/Profile.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import useAuthUser from "../../hooks/AuthUser";
|
||||
|
||||
const Profile = () => {
|
||||
|
||||
const {authUser} = useAuthUser()
|
||||
|
||||
return <>
|
||||
<h1>Profile</h1>
|
||||
<div>
|
||||
<span>Nom: <strong>{authUser?.name}</strong></span>
|
||||
</div>
|
||||
<div>Update name & email</div>
|
||||
<div>Change password</div>
|
||||
<div>Delete Account</div>
|
||||
</>
|
||||
}
|
||||
|
||||
export default Profile
|
||||
51
resources/js/pages/Auth/Register.tsx
Normal file
51
resources/js/pages/Auth/Register.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, {ChangeEvent, FormEvent, SyntheticEvent, useState} from "react"
|
||||
import Field from "../../components/Field";
|
||||
import axios from "axios";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
const Register = () => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [name, setName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const handleSubmit = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
try {
|
||||
await axios.get('/sanctum/csrf-cookie')
|
||||
const res = await axios.post('/api/register', {name, email, password})
|
||||
console.log(name, email, password)
|
||||
navigate('/')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return <div>
|
||||
<form onSubmit={handleSubmit} className="w-96 mx-auto mt-10 border shadow p-3">
|
||||
<h1 className="text-center">S'inscrire</h1>
|
||||
|
||||
<Field placeholder="Nom"
|
||||
name="name"
|
||||
value={name}
|
||||
onChange={event => setName(event.target.value)}
|
||||
autoFocus>Nom</Field>
|
||||
<Field type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={event => setEmail(event.target.value)}
|
||||
autoFocus>Email</Field>
|
||||
<Field type="password"
|
||||
name="password"
|
||||
placeholder="******"
|
||||
value={password}
|
||||
onChange={event => setPassword(event.target.value)}
|
||||
autoFocus>Mot de passe</Field>
|
||||
<button type="submit" className="mt-5 bg-blue-700 w-full block text-white px-5 py-2 text-lg rounded">Valider</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Register
|
||||
11
resources/js/pages/Home.tsx
Normal file
11
resources/js/pages/Home.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react"
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
const Home = () => {
|
||||
|
||||
return <div>Home <Link to="/connexion">Connexion</Link>
|
||||
<Link to="/sinscrire">S'inscrire</Link>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Home
|
||||
11
resources/js/pages/Meteo.tsx
Normal file
11
resources/js/pages/Meteo.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react"
|
||||
import PageLayout from "../components/PageLayout";
|
||||
|
||||
const Meteo = () => {
|
||||
|
||||
return <PageLayout>
|
||||
Météo
|
||||
</PageLayout>
|
||||
}
|
||||
|
||||
export default Meteo
|
||||
65
resources/js/pages/Rainfall.tsx
Normal file
65
resources/js/pages/Rainfall.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, {useEffect, useState} from "react"
|
||||
import PageLayout from "../components/PageLayout";
|
||||
import LastFiveMesure from "../components/rainfall/LastFiveMesure";
|
||||
import AddRainfall from "../components/rainfall/AddRainfall";
|
||||
import RainfallGraph from "../components/rainfall/RainfallGraph";
|
||||
import useAxiosTools from "../hooks/AxiosTools";
|
||||
import {rainfall} from "../types";
|
||||
import Field from "../components/Field";
|
||||
import useDimension from "../hooks/DimensionHook";
|
||||
|
||||
const Rainfall = () => {
|
||||
|
||||
const [loadedAt, reload] = useState(new Date)
|
||||
const [graphData, setGraphData] = useState<rainfall[]>([])
|
||||
const [graphDetails, setGraphDetails] = useState({
|
||||
start_date: (new Date((new Date()).setMonth((new Date).getMonth() - 1))).toSQLDate(),
|
||||
end_date: (new Date()).toSQLDate(),
|
||||
})
|
||||
const {axiosGet} = useAxiosTools()
|
||||
const {targetRef, dimensions} = useDimension()
|
||||
|
||||
useEffect(() => {
|
||||
fetchGraphData()
|
||||
}, [loadedAt])
|
||||
|
||||
useEffect(() => {
|
||||
fetchGraphData()
|
||||
}, [graphDetails])
|
||||
|
||||
const fetchGraphData = async () => {
|
||||
try {
|
||||
const params = `start=${graphDetails.start_date}&end=${graphDetails.end_date}`
|
||||
const res = await axiosGet(`/api/rainfalls/graph?${params}`)
|
||||
setGraphData(res.data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return <PageLayout>
|
||||
<div className="flex justify-between">
|
||||
<LastFiveMesure loadedAt={loadedAt} />
|
||||
<AddRainfall reload={reload} />
|
||||
</div>
|
||||
|
||||
<form className="flex">
|
||||
<Field name="start_date"
|
||||
type="date"
|
||||
value={graphDetails.start_date}
|
||||
onChange={e => setGraphDetails({...graphDetails, start_date: (new Date(e.target.value)).toSQLDate()})} />
|
||||
<Field name="start_date"
|
||||
type="date"
|
||||
value={graphDetails.end_date}
|
||||
onChange={e => setGraphDetails({...graphDetails, end_date: (new Date(e.target.value)).toSQLDate()})} />
|
||||
</form>
|
||||
<div ref={targetRef}>
|
||||
<RainfallGraph width={dimensions.width}
|
||||
height={500}
|
||||
data={graphData} start_date={graphDetails.start_date}
|
||||
end_date={graphDetails.end_date} />
|
||||
</div>
|
||||
</PageLayout>
|
||||
}
|
||||
|
||||
export default Rainfall
|
||||
35
resources/js/pages/Router.tsx
Normal file
35
resources/js/pages/Router.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, {Suspense} from "react";
|
||||
import {BrowserRouter, Link, Route, Routes} from "react-router-dom";
|
||||
import Home from "./Home";
|
||||
import Login from "./Auth/Login";
|
||||
import Register from "./Auth/Register";
|
||||
import useAuthUser from "../hooks/AuthUser";
|
||||
import Profile from "./Auth/Profile";
|
||||
import Header from "../components/Header";
|
||||
import Rainfall from "./Rainfall";
|
||||
import Meteo from "./Meteo";
|
||||
|
||||
const Router = () => {
|
||||
|
||||
const {authUser, loadingAuthUser, logout} = useAuthUser()
|
||||
|
||||
return <>
|
||||
{loadingAuthUser ? '...loading'
|
||||
: <BrowserRouter>
|
||||
<Suspense fallback={'... loading'}>
|
||||
<Header />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
<Route path="/connexion" element={<Login />} />
|
||||
<Route path="/sinscrire" element={<Register />} />
|
||||
<Route path="/meteo" element={<Meteo />} />
|
||||
<Route path="/pluviometrie" element={<Rainfall />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
export default Router
|
||||
5
resources/js/types.ts
Normal file
5
resources/js/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface rainfall {
|
||||
id?: number,
|
||||
date: string,
|
||||
value: number,
|
||||
}
|
||||
20
resources/views/app.blade.php
Normal file
20
resources/views/app.blade.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@viteReactRefresh
|
||||
@vite(['resources/js/app.tsx'])
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
<div id="app" />
|
||||
</body>
|
||||
</html>
|
||||
12
resources/views/mail/reset.blade.php
Normal file
12
resources/views/mail/reset.blade.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<x-mail::message>
|
||||
# Introduction
|
||||
|
||||
The body of your message.
|
||||
|
||||
<x-mail::button :url="''">
|
||||
Button Text
|
||||
</x-mail::button>
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
</x-mail::message>
|
||||
140
resources/views/welcome.blade.php
Normal file
140
resources/views/welcome.blade.php
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user