From 56ada87fdf14772644151cc0d9ebee57b29cc9f5 Mon Sep 17 00:00:00 2001 From: Romulus21 Date: Mon, 25 Dec 2023 14:25:05 +0100 Subject: [PATCH] add month year group for graph data --- app/Http/Controllers/RainfallController.php | 46 ++----- app/Models/Rainfall.php | 63 +++++++++ app/Models/User.php | 3 +- composer.json | 2 +- composer.lock | 28 ++-- database/factories/UserFactory.php | 3 +- .../js/components/rainfall/RainfallGraph.tsx | 16 ++- resources/js/pages/Auth/ForgotPassword.tsx | 2 +- resources/js/pages/Rainfall.tsx | 17 ++- resources/js/types.ts | 10 ++ tests/Feature/RainfallGraphDataTest.php | 121 ++++++++++++++++++ 11 files changed, 248 insertions(+), 63 deletions(-) create mode 100644 tests/Feature/RainfallGraphDataTest.php diff --git a/app/Http/Controllers/RainfallController.php b/app/Http/Controllers/RainfallController.php index f5a5da1..71b08c3 100644 --- a/app/Http/Controllers/RainfallController.php +++ b/app/Http/Controllers/RainfallController.php @@ -7,9 +7,7 @@ use App\Http\Resources\RainfallCollection; use App\Http\Resources\RainfallResource; use App\Models\Rainfall; use Carbon\Carbon; -use Carbon\CarbonPeriod; use Illuminate\Http\Request; -use Illuminate\Support\Collection; class RainfallController extends Controller { @@ -88,48 +86,28 @@ class RainfallController extends Controller $data = $request->validate([ 'start' => 'date', 'end' => 'date', + 'period' => ['regex:/^day|week|month|year$/'], ]); $rainfalls = $request->user() ->rainfalls() ->whereBetween('date', [$data['start'], $data['end']]) ->orderBy('date') - ->get() - ->groupBy('date'); + ->get(); - $results = collect(); - $index = 0; - foreach ($rainfalls as $date => $rainfall) { - if ($index === 0 && $date !== $data['start']) { - [$results, $index] = $this->addEmptyDays($results, $index, $data['start'], (new Carbon($date))->subDay()); - } elseif ($index > 0 && (new Carbon($results->last()['date']))->addDay()->format('Y-m-d') !== $date) { - [$results, $index] = $this->addEmptyDays($results, $index, (new Carbon($results->last()['date']))->addDay(), (new Carbon($date))->subDay()); + $results = []; + $currentDate = $data['start']; + while ($currentDate <= $data['end']) { + $key = Rainfall::getDateKey($currentDate, $data['period']); + if (! isset($results[$key])) { + $keyData = Rainfall::getDateKeyData($currentDate, $data); + $results[$key] = [...$keyData, 'value' => 0]; } - $results->push([ - 'id' => $index++, - 'date' => $date, - 'value' => $rainfall->sum('value'), - ]); + $results[$key]['value'] += $rainfalls->where('date', new Carbon($currentDate))->sum('value'); + $currentDate = (new Carbon($currentDate))->addDay()->format('Y-m-d'); } - if ($results->isNotEmpty() && $results->last()['date'] !== $data['end']) { - [$results] = $this->addEmptyDays($results, $index, (new Carbon($results->last()['date']))->addDay(), $data['end']); - } - - return response()->json($results); - } - - private function addEmptyDays(Collection $results, int $index, Carbon|string $start, Carbon|string $end) - { - foreach (CarbonPeriod::create($start, $end) as $date) { - $results->push([ - 'id' => $index++, - 'date' => $date->format('Y-m-d'), - 'value' => 0, - ]); - } - - return [$results, $index]; + return response()->json(array_values($results)); } } diff --git a/app/Models/Rainfall.php b/app/Models/Rainfall.php index 9eb3b91..a0c1ed9 100644 --- a/app/Models/Rainfall.php +++ b/app/Models/Rainfall.php @@ -2,6 +2,7 @@ namespace App\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -10,6 +11,21 @@ class Rainfall extends Model { use HasFactory; + const MONTHS = [ + 1 => 'Janvier', + 2 => 'Février', + 3 => 'Mars', + 4 => 'Avril', + 5 => 'Mai', + 6 => 'Juin', + 7 => 'Juillet', + 8 => 'Août', + 9 => 'Septembre', + 10 => 'Octobre', + 11 => 'Novembre', + 12 => 'Décembre', + ]; + protected $guarded = []; protected $casts = [ @@ -20,4 +36,51 @@ class Rainfall extends Model { return $this->belongsTo(User::class); } + + public static function getDateKey(string $date, string $period) + { + $date = new Carbon($date); + if ($period === 'year') { + return $date->year; + } elseif ($period === 'month') { + return $date->year * 100 + $date->month; + } elseif ($period === 'week') { + return $date->year * 100 + $date->week; + } + + return $date->format('Y-m-d'); + } + + public static function getDateKeyData(string $date, array $data): array + { + $date = new Carbon($date); + $start = new Carbon($data['start']); + $endDate = new Carbon($data['end']); + $startDate = $date->format('Y-m-d 00:00:00'); + + if ($data['period'] === 'year') { + $days = $start->year === $endDate->year ? $start->diffInDays($endDate) + : (($date->year === $endDate->year || $start->year === $date->year) + ? $date->diffInDays($date->year === $endDate->year ? $endDate : $start->endOfYear()) + 1 + : $date->endOfYear()->day); + $label = $date->year; + } elseif ($data['period'] === 'month') { + $days = ($date->month === $endDate->month || $date->month === $start->month) + ? $date->diffInDays($date->month === $endDate->month ? $endDate : $start->endOfMonth()) + 1 + : $date->endOfMonth()->day; + $label = Rainfall::MONTHS[$date->month].' '.$date->year; + } elseif ($data['period'] === 'week') { + $days = $date->week === $endDate->week ? $date->diffInDays($endDate) + 1 : 7; + $label = 'semaine '.$date->week; + } else { + $days = 1; + $label = $date->format('d/m/Y'); + } + + return [ + 'start' => $startDate, + 'days' => $days, + 'label' => $label, + ]; + } } diff --git a/app/Models/User.php b/app/Models/User.php index f909022..0337d72 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,12 +6,11 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; -use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { - use HasApiTokens, HasFactory, Notifiable; + use HasApiTokens, HasFactory; /** * The attributes that are mass assignable. diff --git a/composer.json b/composer.json index 5d7506e..3dd24c0 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "keywords": ["laravel", "framework"], "license": "MIT", "require": { - "php": "^8.1", + "php": "^8.2", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.10", "laravel/pulse": "^1.0@beta", diff --git a/composer.lock b/composer.lock index 4c1faea..36e1092 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "54c77e612fa554bedbef233532ec3a4a", + "content-hash": "93d8e871a190b9726822fe7e7b71aa1d", "packages": [ { "name": "brick/math", @@ -1099,16 +1099,16 @@ }, { "name": "laravel/framework", - "version": "v10.38.1", + "version": "v10.38.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ced4689f495213e9d23995b36098f12a802cc15b" + "reference": "43da808391da3540d44a8dfeb4e46da4ad8f5723" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ced4689f495213e9d23995b36098f12a802cc15b", - "reference": "ced4689f495213e9d23995b36098f12a802cc15b", + "url": "https://api.github.com/repos/laravel/framework/zipball/43da808391da3540d44a8dfeb4e46da4ad8f5723", + "reference": "43da808391da3540d44a8dfeb4e46da4ad8f5723", "shasum": "" }, "require": { @@ -1300,7 +1300,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-12-20T14:52:12+00:00" + "time": "2023-12-22T14:39:10+00:00" }, { "name": "laravel/prompts", @@ -8179,16 +8179,16 @@ }, { "name": "sebastian/diff", - "version": "5.0.3", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b" + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", "shasum": "" }, "require": { @@ -8201,7 +8201,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -8234,7 +8234,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" }, "funding": [ { @@ -8242,7 +8242,7 @@ "type": "github" } ], - "time": "2023-05-01T07:48:21+00:00" + "time": "2023-12-22T10:55:06+00:00" }, { "name": "sebastian/environment", @@ -9286,7 +9286,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.1" + "php": "^8.2" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index a6ecc0a..ca9d62a 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; /** @@ -21,7 +22,7 @@ class UserFactory extends Factory 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'password' => Hash::make('password'), 'remember_token' => Str::random(10), ]; } diff --git a/resources/js/components/rainfall/RainfallGraph.tsx b/resources/js/components/rainfall/RainfallGraph.tsx index 0a8e8a8..afbb10d 100644 --- a/resources/js/components/rainfall/RainfallGraph.tsx +++ b/resources/js/components/rainfall/RainfallGraph.tsx @@ -1,6 +1,6 @@ import * as d3 from "d3" import React, {FC, LegacyRef, useEffect, useRef} from "react" -import {rainfall} from "../../types"; +import {rainfall, rainfallGraphData} from "../../types"; const RainfallGraph: FC = ({width, height, data, start_date, end_date}) => { @@ -22,14 +22,14 @@ const RainfallGraph: FC = ({width, height, data, start_date, .attr('class', 'relative') .append('g') .attr('transform', "translate(" + margin.left + "," + margin.top + ")") - + console.log(data) 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))]) + .domain([(new Date(start_date)), (new Date()).setDate((new Date(end_date)).getDate())]) .range([margin.left, width - margin.right]) const yAxis = svg.append('g') @@ -71,18 +71,20 @@ const RainfallGraph: FC = ({width, height, data, start_date, .attr("stroke-opacity", 0.1)) } + 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.date)) * (1 - (100 / data.length)/100)) + .attr("x", d => x(new Date(d.start)) + 1.5) .attr("y", d => y(d.value)) - .attr("width", (width - 44 + ((width - 44) / (data.length + 1))) / (data.length + 1) - 2) + .attr("width", d => (dayWidth * d.days - 3)) .attr("height", d => height - margin.bottom - 10 - y(d.value)) .append('title') - .text(d => `${(new Date(d.date)).toLocaleDateString()} : ${d.value}`) + .text(d => `${d.label} : ${d.value}`) } return @@ -93,7 +95,7 @@ export default RainfallGraph interface RainfallGraphProps { width: number, height: number, - data: rainfall[], + data: rainfallGraphData[], start_date: string, end_date: string, } diff --git a/resources/js/pages/Auth/ForgotPassword.tsx b/resources/js/pages/Auth/ForgotPassword.tsx index 5641708..0134261 100644 --- a/resources/js/pages/Auth/ForgotPassword.tsx +++ b/resources/js/pages/Auth/ForgotPassword.tsx @@ -27,7 +27,7 @@ const ForgotPassword = () => {

Mot de passe oublié

- {message &&

Un email vous a été envoyer pour modifier le mot de passe.

} + {message &&

Un email vous a été envoyé pour modifier le mot de passe.

} {errorLabel()} diff --git a/resources/js/pages/Rainfall.tsx b/resources/js/pages/Rainfall.tsx index 14e8454..f2978f4 100644 --- a/resources/js/pages/Rainfall.tsx +++ b/resources/js/pages/Rainfall.tsx @@ -4,17 +4,18 @@ 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 {rainfall, rainfallGraphData} from "../types"; import Field from "../components/Field"; import useDimension from "../hooks/DimensionHook"; const Rainfall = () => { const [loadedAt, reload] = useState(new Date) - const [graphData, setGraphData] = useState([]) + const [graphData, setGraphData] = useState([]) const [graphDetails, setGraphDetails] = useState({ start_date: (new Date((new Date()).setMonth((new Date).getMonth() - 1))).toSQLDate(), end_date: (new Date()).toSQLDate(), + period: 'day', }) const {errorCatch, errorLabel, axiosGet} = useAxiosTools() const {targetRef, dimensions} = useDimension() @@ -25,7 +26,7 @@ const Rainfall = () => { const fetchGraphData = async () => { try { - const params = `start=${graphDetails.start_date}&end=${graphDetails.end_date}` + const params = `start=${graphDetails.start_date}&end=${graphDetails.end_date}&period=${graphDetails.period}` const res = await axiosGet(`/api/rainfalls/graph?${params}`) setGraphData(res.data) } catch (error) { @@ -49,6 +50,16 @@ const Rainfall = () => { type="date" value={graphDetails.end_date} onChange={e => setGraphDetails({...graphDetails, end_date: (new Date(e.target.value)).toSQLDate()})} /> +
+ +
user = User::factory()->create(); + Sanctum::actingAs($this->user); +}); + +test('an user can fetch his rainfalls', function () { + Rainfall::factory()->count(100)->create([ + 'date' => Carbon::today()->subDays(rand(0, 365)), + ]); + + expect($this->user->rainfalls) + ->toHaveCount(100); + + $response = $this->get('/api/rainfalls/graph?start=2023-01-01&end='.now()->format('Y-m-d').'&period=day'); + + $response->assertOk() + ->assertJson([ + [ + 'start' => '2023-01-01 00:00:00', + 'days' => 1, + 'label' => '01/01/2023', + 'value' => $this->user->rainfalls->where('date', '2023-01-01')->sum('value'), + ], + [ + 'start' => '2023-01-02 00:00:00', + 'days' => 1, + 'label' => '02/01/2023', + 'value' => $this->user->rainfalls->where('date', '2023-01-02')->sum('value'), + ], + ]); +}); + +test('an user can fetch his rainfall by week', function () { + Rainfall::factory()->count(100)->create([ + 'date' => Carbon::today()->subDays(rand(0, 365)), + ]); + + $response = $this->get('/api/rainfalls/graph?start=2023-02-01&end='.now()->format('Y-m-d').'&period=week'); + + // $date1 = now(); + // $date2 = now()->addDays(40); + // + // $difference = $date1->diff($date2); + // dd($difference->days); + $response->assertOk() + ->assertJson([ + [ + 'start' => '2023-02-01 00:00:00', + 'days' => 5, + 'label' => 'semaine 6', + 'value' => $this->user->rainfalls->where('date', '2023-02-01')->sum('value'), + ], + [ + 'start' => '2023-02-06 00:00:00', + 'days' => 7, + 'label' => 'semaine 7', + 'value' => $this->user->rainfalls->where('date', '2023-02-01')->sum('value'), + ], + ]); +})->todo(); + +test('an user can fetch his rainfall by month', function () { + Rainfall::factory()->count(100)->create([ + 'date' => Carbon::today()->subDays(rand(0, 365)), + ]); + + $response = $this->get('/api/rainfalls/graph?start=2023-01-15&end='.now()->format('Y-m-d').'&period=month'); + + $response->assertOk() + ->assertJson([ + [ + 'start' => '2023-01-15 00:00:00', + 'days' => 17, + 'label' => 'Janvier 2023', + 'value' => $this->user->rainfalls->whereBetween('date', ['2023-01-15', '2023-01-31'])->sum('value'), + ], + [ + 'start' => '2023-02-01 00:00:00', + 'days' => 28, + 'label' => 'Février 2023', + 'value' => $this->user->rainfalls->whereBetween('date', ['2023-02-01', '2023-02-28'])->sum('value'), + ], + [ + 'start' => '2023-03-01 00:00:00', + 'days' => 31, + 'label' => 'Mars 2023', + 'value' => $this->user->rainfalls->whereBetween('date', ['2023-03-01', '2023-03-31'])->sum('value'), + ], + ]); +}); + +test('an user can fetch his rainfall by year', function () { + Rainfall::factory()->count(100)->create([ + 'date' => Carbon::today()->subDays(rand(0, 365)), + ]); + + $response = $this->get('/api/rainfalls/graph?start=2022-11-15&end='.now()->format('Y-m-d').'&period=year'); + + $response->assertOk() + ->assertJson([ + [ + 'start' => '2022-11-15 00:00:00', + 'days' => 47, + 'label' => 2022, + 'value' => $this->user->rainfalls->whereBetween('date', ['2022-11-15', '2022-12-31'])->sum('value'), + ], + [ + 'start' => '2023-01-01 00:00:00', + 'days' => 359, + 'label' => 2023, + 'value' => $this->user->rainfalls->whereBetween('date', ['2023-01-01', '2023-12-31'])->sum('value'), + ], + ]); +});