add monthly rainfalls

This commit is contained in:
Romulus21
2024-03-09 15:33:23 +01:00
parent 7244cd7be4
commit 77412774c8
9 changed files with 100 additions and 130 deletions

View File

@@ -110,4 +110,27 @@ class RainfallController extends Controller
return response()->json(array_values($results)); return response()->json(array_values($results));
} }
public function lastMonths(Request $request)
{
$result = [];
for ($i = 12; $i >= 0; $i--) {
$date = now()->subMonths($i);
$firstOfMonth = now()->subMonths($i)->firstOfMonth();
$lastOfMonth = now()->subMonths($i)->lastOfMonth();
$rainfalls = $request->user()
->rainfalls()
->whereBetween('date', [$firstOfMonth, $lastOfMonth])
->sum('value');
$result[] = [
'year' => $date->year,
'month' => $date->month,
'label' => $date->monthName.' '.$date->year,
'values' => (int) $rainfalls,
];
}
return response()->json($result);
}
} }

View File

@@ -1,116 +0,0 @@
import * as d3 from "d3"
import React, {FC, useEffect, useRef} from "react"
import {rainfallGraphData} 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
d3.select(svgRef.current).selectAll("*").remove()
const svg = d3.select(svgRef.current)
.attr('id', 'rain-graph')
.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()).setDate((new Date(end_date)).getDate())])
.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)
.ticks(8)
.tickFormat(
// @ts-expect-error change time format
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))
}
const showDetails = (event: Event, data: rainfallGraphData) => {
const toolTip = document.getElementById('tooltip')
if (toolTip) {
toolTip.style.opacity = '1'
toolTip.innerText = `${data.label} : ${data.value}`
}
}
const diffDays = Math.round(Math.abs(((new Date(start_date)).getTime() - (new Date(end_date)).getTime()) / (24 * 60 * 60 * 1000))) + 1
const dayWidth = (width - 44) / diffDays
svg.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("fill", "steelblue")
.attr("x", d => x(new Date(d.start)) + 1.5)
.attr("y", d => y(d.value))
.attr("width", d => (dayWidth * d.days - 3))
.attr("height", d => height - margin.bottom - 10 - y(d.value))
.on('click', showDetails)
.append('title')
.text(d => `${d.label} : ${d.value}`)
}
return <div className="relative">
<svg ref={svgRef} />
<div id="tooltip" className="absolute left-10 top-3 rounded border p-2" style={{opacity: 0}}></div>
</div>
}
export default RainfallGraph
interface RainfallGraphProps {
width: number,
height: number,
data: rainfallGraphData[],
start_date: string,
end_date: string,
}

View File

@@ -0,0 +1,52 @@
import React, {useState, useEffect} from "react"
import Card from "../Card"
import useAxiosTools from "../../hooks/AxiosTools"
import {AxiosError} from "axios"
import {monthlyRainfall} from "../../types"
const YearRainfall = () => {
const {errorCatch, errorLabel, setError, axiosGet} = useAxiosTools()
const [data, setData] = useState<monthlyRainfall[]>([])
const months = Array(13)
.reduce((result, item, index) => {
const date = new Date()
console.log(item, index, date)
return item
}, [])
console.log(months)
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
try {
const res = await axiosGet('/api/rainfalls/last-months')
setData(res.data)
} catch (e) {
if (e instanceof AxiosError) {
setError(e.message)
} else {
errorCatch(e)
}
}
}
return <div>
<Card className="w-full min-w-[300px] self-start overflow-hidden md:w-auto">
<h1 className="-mx-2 -mt-1 bg-blue-500 px-2 py-1 text-center text-lg font-bold text-white">Précipitations des derniers mois</h1>
{errorLabel()}
<table className="w-full text-center">
<tbody>
{data.map(line => <tr key={line.year + '-' + line.month} className="">
<td>{line.label}</td>
<td className="px-2 text-right">{line.values}</td>
</tr>)}
</tbody>
</table>
</Card>
</div>
}
export default YearRainfall

View File

@@ -4,7 +4,7 @@ const useDimension = () => {
const RESET_TIMEOUT = 300 const RESET_TIMEOUT = 300
let movement_timer: number|undefined = undefined let movement_timer: number|undefined = undefined
const targetRef = useRef<HTMLDivElement>() const targetRef = useRef<HTMLDivElement|undefined>()
const [dimensions, setDimensions] = useState({ width:0, height: 0 }) const [dimensions, setDimensions] = useState({ width:0, height: 0 })
useEffect(() => { useEffect(() => {

View File

@@ -1,6 +1,7 @@
import React from "react" import React from "react"
import useAuthUser from "../hooks/AuthUser" import useAuthUser from "../hooks/AuthUser"
import Rainfall from "./Rainfall" import YearRainfall from "../components/rainfall/YearRainfaill"
import PageLayout from "../components/PageLayout"
const Home = () => { const Home = () => {
@@ -8,7 +9,9 @@ const Home = () => {
return <div> return <div>
{authUser {authUser
? <Rainfall /> ? <PageLayout>
<YearRainfall />
</PageLayout>
: <div className="px-5 pt-10"> : <div className="px-5 pt-10">
<h1 className="text-lg font-bold">Application pour enregistrer sa pluviométrie</h1> <h1 className="text-lg font-bold">Application pour enregistrer sa pluviométrie</h1>
<p className="mt-5">Un compte est nécessaire mais les inscriptions ne sont pas ouvertes.</p> <p className="mt-5">Un compte est nécessaire mais les inscriptions ne sont pas ouvertes.</p>

View File

@@ -1,14 +1,14 @@
import React, {useEffect, useState} from "react" import React, {useEffect, useState} from "react"
import PageLayout from "../components/PageLayout" import PageLayout from "../../components/PageLayout"
import LastFiveMesure from "../components/rainfall/LastFiveMesure" import LastFiveMesure from "../../components/rainfall/LastFiveMesure"
import AddRainfall from "../components/rainfall/AddRainfall" import AddRainfall from "../../components/rainfall/AddRainfall"
import useAxiosTools from "../hooks/AxiosTools" import useAxiosTools from "../../hooks/AxiosTools"
import {rainfallGraphData} from "../types" import {rainfallGraphData} from "../../types"
import Field from "../components/Field" import Field from "../../components/Field"
import useDimension from "../hooks/DimensionHook" import useDimension from "../../hooks/DimensionHook"
import RainFallEcharts from "../components/rainfall/RainFallEcharts" import RainFallEcharts from "../../components/rainfall/RainFallEcharts"
const Rainfall = () => { const RainfallGraph = () => {
const [loadedAt, reload] = useState(new Date) const [loadedAt, reload] = useState(new Date)
const [graphData, setGraphData] = useState<rainfallGraphData[]>([]) const [graphData, setGraphData] = useState<rainfallGraphData[]>([])
@@ -77,4 +77,4 @@ const Rainfall = () => {
</PageLayout> </PageLayout>
} }
export default Rainfall export default RainfallGraph

View File

@@ -9,7 +9,7 @@ const Home = lazy(() => import('./Home'))
const Login = lazy(() => import('./Auth/Login')) const Login = lazy(() => import('./Auth/Login'))
const Profile = lazy(() => import('./Auth/Profile')) const Profile = lazy(() => import('./Auth/Profile'))
const Reset = lazy(() => import('./Auth/Reset')) const Reset = lazy(() => import('./Auth/Reset'))
const Rainfall = lazy(() => import('./Rainfall')) const Rainfall = lazy(() => import('./Rainfall/RainfallGraph'))
const RainfallIndex = lazy(() => import('./Rainfall/RainfallIndex')) const RainfallIndex = lazy(() => import('./Rainfall/RainfallIndex'))
const Router = () => { const Router = () => {

View File

@@ -14,6 +14,13 @@ export interface rainfallGraphData {
value: number, value: number,
} }
export interface monthlyRainfall {
year: number,
month: number,
label: string,
values: number,
}
export interface WeatherRequest { export interface WeatherRequest {
list: WeatherValue[], list: WeatherValue[],
} }

View File

@@ -28,6 +28,7 @@ Route::middleware('auth:sanctum')->group(function () {
Route::post('/locations', [LocationController::class, 'store'])->name('location.store'); Route::post('/locations', [LocationController::class, 'store'])->name('location.store');
Route::get('/rainfalls/last-months', [RainfallController::class, 'lastMonths'])->name('rainfalls.last');
Route::get('/rainfalls/last', [RainfallController::class, 'lastRainfalls'])->name('rainfalls.last'); Route::get('/rainfalls/last', [RainfallController::class, 'lastRainfalls'])->name('rainfalls.last');
Route::get('/rainfalls/graph', [RainfallController::class, 'graphValue'])->name('rainfalls.graph'); Route::get('/rainfalls/graph', [RainfallController::class, 'graphValue'])->name('rainfalls.graph');