Merge branch 'trunk'
This commit is contained in:
commit
41f51b1718
19 changed files with 1270 additions and 518 deletions
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Brands\GetBrandUrlsRequest;
|
||||
use App\Http\Requests\Bo\Brands\StoreBrandRequest;
|
||||
use App\Http\Requests\Bo\Brands\UpdateBrandRequest;
|
||||
use App\Models\Brand;
|
||||
|
|
@ -124,6 +125,7 @@ class BrandController extends Controller
|
|||
));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
|
|
@ -148,4 +150,21 @@ class BrandController extends Controller
|
|||
['model' => Str::singular(trans('models.classes.brands'))]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get urls from a given brand.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Brands\GetBrandUrlsRequest $request
|
||||
* @param \App\Models\Brand $brand
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getUrls(GetBrandUrlsRequest $request, Brand $brand)
|
||||
{
|
||||
if (!$request->wantsJson()) {
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
return response()
|
||||
->json($brand->urls);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
333
app/Http/Controllers/Bo/StatsController.php
Normal file
333
app/Http/Controllers/Bo/StatsController.php
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\StatsRequest;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Url;
|
||||
use App\Models\UrlStats;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Colors\RandomColor;
|
||||
|
||||
class StatsController extends Controller
|
||||
{
|
||||
/** @var array Colors used in charts */
|
||||
private $colors = [];
|
||||
|
||||
/**
|
||||
* Stats Page
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\StatsRequest $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(StatsRequest $request)
|
||||
{
|
||||
$brandModels = Brand::all();
|
||||
|
||||
$chart = null;
|
||||
$start = $request->start ? Carbon::parse($request->start) : null;
|
||||
$end = $request->end ? Carbon::parse($request->end) : null;
|
||||
$period = $request->period;
|
||||
|
||||
if ($request->post()) {
|
||||
$periodInterval = CarbonPeriod::create($start, $end);
|
||||
$chart = $this->getChart($request);
|
||||
} //end if
|
||||
return view('bo.pages.stats', compact('brandModels', 'chart', 'start', 'end'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chart from ChartJs.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return object
|
||||
*/
|
||||
private function getChart(Request $request)
|
||||
{
|
||||
$period = $request->period;
|
||||
$periodInterval = $this->getDateInterval($request->start, $request->end, $request->period);
|
||||
foreach ($periodInterval as $date) {
|
||||
$labels[] = $this->getChartLabels($date, $request->period);
|
||||
}
|
||||
|
||||
if (!$request->brand) {
|
||||
$datasets = $this->getGlobalDatas($periodInterval, Brand::all(), $period);
|
||||
} elseif (!$request->url) {
|
||||
$datasets = $this->getBrandDatas($periodInterval, Brand::find($request->brand), $period);
|
||||
} else {
|
||||
$datasets = $this->getUrlDatas($periodInterval, Url::find($request->url), $period);
|
||||
}
|
||||
|
||||
if (count(Arr::first($datasets)['data']) > 1) {
|
||||
$type = 'line';
|
||||
} else {
|
||||
$type = 'bar';
|
||||
}
|
||||
return app()->chartjs
|
||||
->name('stats')
|
||||
->type($type)
|
||||
->labels($labels)
|
||||
->datasets($datasets)
|
||||
->optionsRaw($options ?? "{}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get datas for all brands.
|
||||
*
|
||||
* @param \Carbon\CarbonPeriod $periodInterval
|
||||
* @param \Illuminate\Database\Eloquent\Collection $brandModels
|
||||
* @param string $period
|
||||
* @return array
|
||||
*/
|
||||
private function getGlobalDatas(
|
||||
CarbonPeriod $periodInterval,
|
||||
\Illuminate\Database\Eloquent\Collection $brandModels,
|
||||
string $period = "day"
|
||||
) {
|
||||
foreach ($brandModels as $brand) {
|
||||
foreach ($periodInterval as $date) {
|
||||
switch ($period) {
|
||||
case 'day':
|
||||
$dailyStats = UrlStats::where('date', $date)
|
||||
->whereIn('url_id', $brand->urls->pluck('id'))
|
||||
->get();
|
||||
break;
|
||||
case 'week':
|
||||
$dailyStats = UrlStats::whereIn(
|
||||
'date',
|
||||
CarbonPeriod::create(
|
||||
$date->startOfWeek()->format('Y-m-d'),
|
||||
$date->endOfWeek()->format('Y-m-d')
|
||||
)->toArray()
|
||||
)->whereIn('url_id', $brand->urls->pluck('id'))
|
||||
->get();
|
||||
break;
|
||||
case 'month':
|
||||
$dailyStats = UrlStats::whereIn(
|
||||
'date',
|
||||
CarbonPeriod::create(
|
||||
$date->startOfMonth()->format('Y-m-d'),
|
||||
$date->endOfMonth()->format('Y-m-d')
|
||||
)->toArray()
|
||||
)->whereIn('url_id', $brand->urls->pluck('id'))
|
||||
->get();
|
||||
break;
|
||||
} //end switch
|
||||
$data[$brand->id][] = $dailyStats->sum('calls');
|
||||
} //end foreach
|
||||
|
||||
$color = $this->getUniqueColor();
|
||||
|
||||
$datasets[] = [
|
||||
'label' => $brand->name,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
"pointBorderColor" => $color,
|
||||
"pointBackgroundColor" => $color,
|
||||
'data' => $data[$brand->id],
|
||||
'lineTension' => 0.4,
|
||||
];
|
||||
} //end foreach
|
||||
return $datasets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chart for all urls of a given brand.
|
||||
*
|
||||
* @param \Carbon\CarbonPeriod $periodInterval
|
||||
* @param \App\Models\Brand $brand
|
||||
* @param string $period
|
||||
* @return object
|
||||
*/
|
||||
private function getBrandDatas(
|
||||
CarbonPeriod $periodInterval,
|
||||
\App\Models\Brand $brand,
|
||||
string $period = "day"
|
||||
) {
|
||||
foreach ($brand->urls as $url) {
|
||||
foreach ($periodInterval as $date) {
|
||||
switch ($period) {
|
||||
case 'day':
|
||||
$dailyStats = UrlStats::where('date', $date)
|
||||
->where('url_id', $url->id)
|
||||
->get();
|
||||
break;
|
||||
case 'week':
|
||||
$dailyStats = UrlStats::whereIn(
|
||||
'date',
|
||||
CarbonPeriod::create(
|
||||
$date->startOfWeek()->format('Y-m-d'),
|
||||
$date->endOfWeek()->format('Y-m-d')
|
||||
)->toArray()
|
||||
)->where('url_id', $url->id)
|
||||
->get();
|
||||
break;
|
||||
case 'month':
|
||||
$dailyStats = UrlStats::whereIn(
|
||||
'date',
|
||||
CarbonPeriod::create(
|
||||
$date->startOfMonth()->format('Y-m-d'),
|
||||
$date->endOfMonth()->format('Y-m-d')
|
||||
)->toArray()
|
||||
)->where('url_id', $url->id)
|
||||
->get();
|
||||
break;
|
||||
} //end switch
|
||||
|
||||
$data[$url->id][] = $dailyStats->sum('calls');
|
||||
} //end foreach
|
||||
|
||||
$color = $this->getUniqueColor();
|
||||
|
||||
$datasets[] = [
|
||||
'label' => $url->value,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
"pointBorderColor" => $color,
|
||||
"pointBackgroundColor" => $color,
|
||||
'data' => $data[$url->id],
|
||||
'lineTension' => 0.4,
|
||||
];
|
||||
} //end foreach
|
||||
return $datasets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chart for a given url.
|
||||
*
|
||||
* @param \Carbon\CarbonPeriod $periodInterval
|
||||
* @param \App\Models\Url $url
|
||||
* @param string $period
|
||||
* @return object
|
||||
*/
|
||||
private function getUrlDatas(
|
||||
CarbonPeriod $periodInterval,
|
||||
\App\Models\Url $url,
|
||||
string $period = 'day'
|
||||
) {
|
||||
foreach ($periodInterval as $date) {
|
||||
switch ($period) {
|
||||
case 'day':
|
||||
$dailyStats = UrlStats::where('date', $date)
|
||||
->where('url_id', $url->id)
|
||||
->get();
|
||||
break;
|
||||
case 'week':
|
||||
$dailyStats = UrlStats::whereIn(
|
||||
'date',
|
||||
CarbonPeriod::create(
|
||||
$date->startOfWeek()->format('Y-m-d'),
|
||||
$date->endOfWeek()->format('Y-m-d')
|
||||
)->toArray()
|
||||
)->where('url_id', $url->id)
|
||||
->get();
|
||||
break;
|
||||
case 'month':
|
||||
$dailyStats = UrlStats::whereIn(
|
||||
'date',
|
||||
CarbonPeriod::create(
|
||||
$date->startOfMonth()->format('Y-m-d'),
|
||||
$date->endOfMonth()->format('Y-m-d')
|
||||
)->toArray()
|
||||
)->where('url_id', $url->id)
|
||||
->get();
|
||||
break;
|
||||
} //end switch
|
||||
$data[$url->id][] = $dailyStats->sum('calls');
|
||||
} //end foreach
|
||||
|
||||
$color = $this->getUniqueColor();
|
||||
|
||||
$datasets[] = [
|
||||
'label' => $url->value,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
"pointBorderColor" => $color,
|
||||
"pointBackgroundColor" => $color,
|
||||
'data' => $data[$url->id],
|
||||
'lineTension' => 0.4,
|
||||
];
|
||||
return $datasets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CarbonPeriod for the given period.
|
||||
*
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @param string $period Can be day|week|month.
|
||||
* @return \Carbon\CarbonPeriod
|
||||
*/
|
||||
private function getDateInterval(string $start, string $end, string $period)
|
||||
{
|
||||
switch ($period) {
|
||||
case 'day':
|
||||
$periodInterval = CarbonPeriod::create($start, $end);
|
||||
break;
|
||||
case 'week':
|
||||
$periodInterval = CarbonPeriod::create(
|
||||
Carbon::parse($start)->startOfWeek(),
|
||||
Carbon::parse($end)->endOfWeek(),
|
||||
'1 week'
|
||||
);
|
||||
break;
|
||||
case 'month':
|
||||
$periodInterval = CarbonPeriod::create(
|
||||
Carbon::parse($start)->startOfMonth(),
|
||||
Carbon::parse($end)->endOfMonth(),
|
||||
'1 month'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$periodInterval = CarbonPeriod::create($start, $end);
|
||||
} //end switch
|
||||
return $periodInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get labels for charts.
|
||||
*
|
||||
* @param Carbon\Carbon $date
|
||||
* @param string $period Can be day|week|month.
|
||||
* @return string
|
||||
*/
|
||||
private function getChartLabels(Carbon $date, string $period)
|
||||
{
|
||||
$labels = [];
|
||||
if ($period === "day") {
|
||||
$labels = array_merge($labels, [$date->format('d-m-Y')]);
|
||||
}
|
||||
if ($period === "week") {
|
||||
$labels = array_unique(array_merge($labels, [sprintf(
|
||||
'Du %s au %s',
|
||||
$date->startOfWeek()->format('Y-m-d'),
|
||||
$date->endOfWeek()->format('Y-m-d')
|
||||
)]));
|
||||
}
|
||||
if ($period === "month") {
|
||||
$labels = array_unique(array_merge($labels, [Str::ucfirst($date->monthName)]));
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* getUniqueColor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getUniqueColor()
|
||||
{
|
||||
$color = RandomColor::one();
|
||||
|
||||
if (in_array($color, $this->colors)) {
|
||||
return $this->getUniqueColor();
|
||||
}
|
||||
$this->colors[] = $color;
|
||||
return $color;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,8 @@ class UrlController extends Controller
|
|||
$urls,
|
||||
$request->search,
|
||||
null,
|
||||
'name',
|
||||
'key',
|
||||
'value'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
28
app/Http/Requests/Bo/Brands/GetBrandUrlsRequest.php
Normal file
28
app/Http/Requests/Bo/Brands/GetBrandUrlsRequest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Brands;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class GetBrandUrlsRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
69
app/Http/Requests/Bo/StatsRequest.php
Normal file
69
app/Http/Requests/Bo/StatsRequest.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use App\Models\Brand;
|
||||
use App\Models\Url;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StatsRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
// Reset brand and url if no value is selected.
|
||||
'url' => Url::find($this->url) ? $this->url : null,
|
||||
'brand' => Brand::find($this->brand) ? $this->brand : null,
|
||||
// End date can't be after today.
|
||||
'end' => Carbon::parse($this->end) > now() ? now()->toDateString() : $this->end,
|
||||
]);
|
||||
if ($this->period === 'week') {
|
||||
$this->merge([
|
||||
'start' => Carbon::parse($this->start)->startOfWeek()->toDateString(),
|
||||
'end' => Carbon::parse($this->end)->endOfWeek()->toDateString(),
|
||||
]);
|
||||
}
|
||||
if ($this->period === 'month') {
|
||||
$this->merge([
|
||||
'start' => Carbon::parse($this->start)->startOfMonth()->toDateString(),
|
||||
'end' => Carbon::parse($this->end)->endOfMonth()->toDateString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
if (request()->post()) {
|
||||
return [
|
||||
'period' => 'required|string|in:day,week,month',
|
||||
'start' => 'required|date|date_format:Y-m-d|before_or_equal:today|after_or_equal:'
|
||||
. Carbon::parse($this->end)->modify('-1 year'),
|
||||
'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
|
||||
'brand' => 'nullable|integer|exists:App\Models\Brand,id',
|
||||
'url' => 'nullable|integer|exists:App\Models\Url,id'
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
"php": "^7.3|^8.0",
|
||||
"diglactic/laravel-breadcrumbs": "^6.1",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"fx3costa/laravelchartjs": "^3.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"kwaadpepper/enum": "^2.1",
|
||||
"kwaadpepper/image-resizer": "^0.1.2",
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
"laravel/ui": "^3.4",
|
||||
"ldc/web-admin-secure-laravel": "^1.0",
|
||||
"mews/purifier": "^3.3",
|
||||
"mistic100/randomcolor": "^1.0",
|
||||
"sentry/sentry-laravel": "^3.2",
|
||||
"spatie/laravel-mix-preload": "^1.3"
|
||||
},
|
||||
|
|
|
|||
102
composer.lock
generated
102
composer.lock
generated
|
|
@ -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": "a016428bf29d21ef8e8f541266808f09",
|
||||
"content-hash": "c39af8f51ec628f1255518fec5ae3659",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
|
@ -745,6 +745,62 @@
|
|||
"abandoned": true,
|
||||
"time": "2022-02-23T14:25:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fx3costa/laravelchartjs",
|
||||
"version": "3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fxcosta/laravel-chartjs.git",
|
||||
"reference": "255154a4a6b57fb146eba4fdedcef4d1fe075e68"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/fxcosta/laravel-chartjs/zipball/255154a4a6b57fb146eba4fdedcef4d1fe075e68",
|
||||
"reference": "255154a4a6b57fb146eba4fdedcef4d1fe075e68",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/support": "^5.1|^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"php": ">=5.6.4"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Fx3costa\\LaravelChartJs\\Providers\\ChartjsServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fx3costa\\LaravelChartJs\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Felix",
|
||||
"email": "fx3costa@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Simple package to facilitate and automate the use of charts in Laravel 5.x using Chartjs v2 library",
|
||||
"keywords": [
|
||||
"chart",
|
||||
"chartjs",
|
||||
"fx3costa",
|
||||
"graphics",
|
||||
"laravel5",
|
||||
"reports"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/fxcosta/laravel-chartjs/issues",
|
||||
"source": "https://github.com/fxcosta/laravel-chartjs/tree/3.0.0"
|
||||
},
|
||||
"time": "2023-02-23T12:23:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.1",
|
||||
|
|
@ -2279,6 +2335,50 @@
|
|||
},
|
||||
"time": "2022-07-20T12:14:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mistic100/randomcolor",
|
||||
"version": "1.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mistic100/RandomColor.php.git",
|
||||
"reference": "527635e9d632ea216c91baf1ffb51f4c81d763de"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mistic100/RandomColor.php/zipball/527635e9d632ea216c91baf1ffb51f4c81d763de",
|
||||
"reference": "527635e9d632ea216c91baf1ffb51f4c81d763de",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Colors\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Damien \"Mistic\" Sorel",
|
||||
"email": "contact@git.strangeplanet.fr",
|
||||
"homepage": "http://www.strangeplanet.fr"
|
||||
}
|
||||
],
|
||||
"description": "Generate attractive random colors",
|
||||
"homepage": "https://github.com/mistic100/RandomColor.php",
|
||||
"keywords": [
|
||||
"color",
|
||||
"generator",
|
||||
"random"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mistic100/RandomColor.php/issues",
|
||||
"source": "https://github.com/mistic100/RandomColor.php/tree/1.0.6"
|
||||
},
|
||||
"time": "2022-10-13T11:40:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "2.9.1",
|
||||
|
|
|
|||
|
|
@ -167,6 +167,8 @@ return [
|
|||
* Package Service Providers...
|
||||
*/
|
||||
|
||||
Fx3costa\LaravelChartJs\Providers\ChartjsServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
*/
|
||||
|
|
@ -230,6 +232,8 @@ return [
|
|||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
'Carbon' => Illuminate\Support\Carbon::class,
|
||||
|
||||
|
||||
],
|
||||
|
||||
|
|
|
|||
975
package-lock.json
generated
975
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -22,6 +22,7 @@
|
|||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@sipec/vue3-tags-input": "^3.0.4",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/chart.js": "^2.9.38",
|
||||
"@types/cropperjs": "^1.3.0",
|
||||
"@types/toastify-js": "^1.11.1",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"axios": "^0.21",
|
||||
"bootstrap": "^5.2.3",
|
||||
"chart.js": "^2.0.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cropperjs": "^1.5.12",
|
||||
"eslint": "^8.23.0",
|
||||
|
|
@ -42,12 +44,13 @@
|
|||
"laravel-mix-stylelint": "^1.0.0",
|
||||
"postcss": "^8.1.14",
|
||||
"prettier": "^2.7.1",
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.54.8",
|
||||
"sass-loader": "^12.6.0",
|
||||
"slugify": "^1.6.5",
|
||||
"stylelint": "^14.11.0",
|
||||
"stylelint-config-idiomatic-order": "^8.1.0",
|
||||
"stylelint-config-idiomatic-order": "^9.0.0",
|
||||
"stylelint-config-sass-guidelines": "^9.0.1",
|
||||
"stylelint-config-standard-scss": "^6.1.0",
|
||||
"stylelint-webpack-plugin": "^3.3.0",
|
||||
|
|
@ -59,8 +62,5 @@
|
|||
"vue": "^3.2.38",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-loader": "^16.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"qr-code-styling": "^1.6.0-rc.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,3 +11,6 @@
|
|||
@import "./base/fonts";
|
||||
@import "./base/classes";
|
||||
@import "./base/common";
|
||||
|
||||
// * PAGES
|
||||
@import "./pages/stats";
|
||||
|
|
|
|||
1
resources/sass/bo/pages/stats.scss
Normal file
1
resources/sass/bo/pages/stats.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Empty.
|
||||
|
|
@ -160,9 +160,9 @@ p {
|
|||
padding: 0;
|
||||
border-width: 1px;
|
||||
border-color: color.scale($body-bg, $lightness: -30%);
|
||||
border-radius: 2px;
|
||||
margin: 0 auto;
|
||||
background-color: $body-bg;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 0 rgb(0 0 150 / 2.5%), 2px 4px 0 rgb(0 0 150 / 1.5%);
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// UTILITIES
|
||||
import "./utilities/bootstrap";
|
||||
import "./utilities/chartjs";
|
||||
import "./utilities/fontawesome";
|
||||
|
||||
// MODULES
|
||||
|
|
@ -8,3 +9,4 @@ import "./modules/sweetalert";
|
|||
// CUSTOM
|
||||
import "./bo/common";
|
||||
import "./bo/vue-bo";
|
||||
|
||||
|
|
|
|||
6
resources/ts/index.d.ts
vendored
6
resources/ts/index.d.ts
vendored
|
|
@ -1,6 +1,7 @@
|
|||
export {};
|
||||
import { AxiosStatic } from "axios";
|
||||
export { };
|
||||
import type VueTagsInput from "@sipec/vue3-tags-input";
|
||||
import { AxiosStatic } from "axios";
|
||||
import { Chart } from "chart.js";
|
||||
|
||||
// * Enum
|
||||
interface EnumInterface {
|
||||
|
|
@ -72,6 +73,7 @@ declare global {
|
|||
|
||||
interface Window {
|
||||
axios: AxiosStatic;
|
||||
Chart: typeof Chart;
|
||||
Swal: Function;
|
||||
vueDebug: boolean | undefined;
|
||||
__SYSTEM: {
|
||||
|
|
|
|||
2
resources/ts/utilities/chartjs.ts
Normal file
2
resources/ts/utilities/chartjs.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import { Chart } from "chart.js";
|
||||
window.Chart = Chart;
|
||||
|
|
@ -8,6 +8,14 @@
|
|||
{{ __('Dashboard') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.stats.*')) active @endif"
|
||||
href="{{ route('bo.stats.index') }}"
|
||||
title="{{ __('Accéder aux statistiques') }}"
|
||||
data-bs-toggle="tooltip">
|
||||
{{ __('Stats') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.brands.index')) active @endif"
|
||||
href="{{ route('bo.brands.index') }}"
|
||||
|
|
|
|||
171
resources/views/bo/pages/stats.blade.php
Normal file
171
resources/views/bo/pages/stats.blade.php
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
@extends('bo.layout')
|
||||
|
||||
@section('title', __('Accueil'))
|
||||
@section('description', __('Description site'))
|
||||
@section('breadcrumb', 'no')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card" id="main-card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-2 pt-3">
|
||||
<div class="d-flex align-items-end">
|
||||
<img class="d-none d-print-block me-2" src="{{ mix('assets/images/logo.png', 'build') }}"
|
||||
style="height: 2rem">
|
||||
<h1 class="fs-2 mb-0"><span class="fw-bold">
|
||||
{{ __('Statistiques') }}
|
||||
</h1>
|
||||
@if (!empty($start) && !empty($end))
|
||||
<p class="fw-bold mb-0 ms-4">
|
||||
{{ __('Période du') }} {{ $start->isoFormat('ll') }} - {{ $end->isoFormat('ll') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{-- Top-bar --}}
|
||||
<div class="row justify-content-end d-print-none">
|
||||
<div class="col-12">
|
||||
<form action="{{ url()->current() }}" method="POST">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-xl-3 w-auto">
|
||||
<div class="input-group mb-3" id="matomo-options-bar">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="period-day">{{ __('Jour') }}</label>
|
||||
<input class="form-check-input" id="period-day" name="period" type="radio" value="day"
|
||||
@if (request('period') === 'day' || empty(request('period'))) checked @endif>
|
||||
</div>
|
||||
<div class="form-check ms-2">
|
||||
<label class="form-check-label" for="period-week">{{ __('Semaine') }}</label>
|
||||
<input class="form-check-input" id="period-week" name="period" type="radio"
|
||||
value="week" @if (request('period') === 'week') checked @endif>
|
||||
</div>
|
||||
<div class="form-check ms-2">
|
||||
<label class="form-check-label" for="period-month">{{ __('Mois') }}</label>
|
||||
<input class="form-check-input" id="period-month" name="period" type="radio"
|
||||
value="month" @if (request('period') === 'month') checked @endif>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">{{ __('Début') }}</span>
|
||||
<input class="form-control date start" name="start" type="date"
|
||||
value="{{ !empty($start)? $start->format('Y-m-d'): Carbon::now()->startOfMonth()->format('Y-m-d') }}"
|
||||
required>
|
||||
<span class="input-group-text">{{ __('Fin') }}</span>
|
||||
<input class="form-control date end" name="end" type="date"
|
||||
value="{{ !empty($end) ? $end->format('Y-m-d') : Carbon::now()->format('Y-m-d') }}" required>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ __('Visualiser') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-5">
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="inputGroupBrands">{{ __('Marque') }}</label>
|
||||
<select class="form-select" id="inputGroupBrands" name="brand">
|
||||
<option id="baseBrandOption" selected>{{ __('Sélectionnez une marque') }}</option>
|
||||
@foreach ($brandModels as $brand)
|
||||
<option value="{{ $brand->id }}" @if ($brand->id == request()->brand) selected @endif>
|
||||
{{ $brand->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-7 d-none" id="urlSelect">
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="inputGroupUrls">{{ __('URL') }}</label>
|
||||
<select class="form-select" id="inputGroupUrls" name="url">
|
||||
<option selected>{{ __('Sélectionnez une url') }}</option>
|
||||
@if (\App\Models\Brand::find(request()->brand))
|
||||
@foreach (\App\Models\Brand::find(request()->brand)->urls as $url)
|
||||
<option value="{{ $url->id }}"
|
||||
@if ($url->id == request()->url) selected @endif>
|
||||
{{ $url->value }}
|
||||
</option>
|
||||
@endforeach
|
||||
@endif
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{-- Stats card --}}
|
||||
@if ($chart)
|
||||
<div class="card mb-3 mt-3">
|
||||
<div class="card-body">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 chart">
|
||||
{!! $chart->render() !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Allow url selection if a brand is selected.
|
||||
const brandInput = document.getElementById('inputGroupBrands');
|
||||
const urlInput = document.getElementById('urlSelect');
|
||||
let urls = [];
|
||||
|
||||
if (brandInput.value !== document.getElementById('baseBrandOption').textContent) {
|
||||
urlInput.classList.remove('d-none');
|
||||
}
|
||||
|
||||
brandInput.addEventListener('change', (e) => {
|
||||
if (brandInput.value !== document.getElementById('baseBrandOption').textContent) {
|
||||
urlInput.classList.remove('d-none');
|
||||
let fetchUrl = "{{ route('bo.brands.geturls', ':id') }}"
|
||||
fetchUrl = fetchUrl.replace(':id', brandInput.value);
|
||||
axios
|
||||
.get(fetchUrl)
|
||||
.then((response) => {
|
||||
urls = response.data;
|
||||
displayUrlInput(urls);
|
||||
})
|
||||
} else {
|
||||
urlInput.classList.add('d-none');
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function displayUrlInput(urls) {
|
||||
const urlSelectInput = document.getElementById('inputGroupUrls');
|
||||
// Empty select input.
|
||||
for (let i = urlSelectInput.options.length - 1; i >= 0; i--) {
|
||||
urlSelectInput.remove(i);
|
||||
}
|
||||
|
||||
// Set default option.
|
||||
let defaultOption = new Option();
|
||||
defaultOption.label = 'Sélectionnez une url';
|
||||
urlSelectInput.appendChild(defaultOption);
|
||||
// Set an option for each url.
|
||||
for (let url of urls) {
|
||||
let option = new Option();
|
||||
option.label = url.value;
|
||||
option.value = url.id;
|
||||
urlSelectInput.appendChild(option);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -2,27 +2,41 @@
|
|||
|
||||
use App\Http\Controllers\Bo\BrandController;
|
||||
use App\Http\Controllers\Bo\DashboardController;
|
||||
use App\Http\Controllers\Bo\StatsController;
|
||||
use App\Http\Controllers\Bo\UrlController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('bo')->name('bo.')->middleware(['admin-secure', 'throttle:credentials'])->group(function () {
|
||||
Route::namespace('\App\Http\Controllers\Bo')->group(function () {
|
||||
Auth::routes([
|
||||
'login' => true,
|
||||
'logout' => true,
|
||||
'reset' => false,
|
||||
'register' => false,
|
||||
'confirm' => false,
|
||||
'verify' => false
|
||||
]);
|
||||
});
|
||||
Route::prefix('bo')
|
||||
->name('bo.')
|
||||
->middleware(['admin-secure', 'throttle:credentials'])
|
||||
->group(function () {
|
||||
// Authenticate routes.
|
||||
Route::namespace('\App\Http\Controllers\Bo')
|
||||
->group(function () {
|
||||
Auth::routes([
|
||||
'login' => true,
|
||||
'logout' => true,
|
||||
'reset' => false,
|
||||
'register' => false,
|
||||
'confirm' => false,
|
||||
'verify' => false
|
||||
]);
|
||||
});
|
||||
|
||||
Route::middleware('auth:backend')->group(function () {
|
||||
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
|
||||
// Brands routes.
|
||||
Route::resource('/brands', BrandController::class);
|
||||
// Urls routes.
|
||||
Route::resource('/urls', UrlController::class);
|
||||
Route::middleware('auth:backend')
|
||||
->group(function () {
|
||||
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
|
||||
|
||||
// Stats routes.
|
||||
Route::get('/stats', [StatsController::class, 'index'])->name('stats.index');
|
||||
Route::post('/stats', [StatsController::class, 'index'])->name('stats.show');
|
||||
|
||||
// Brands routes.
|
||||
Route::resource('/brands', BrandController::class);
|
||||
Route::get('/brand/{brand}/url', [BrandController::class, 'getUrls'])->name('brands.geturls');
|
||||
|
||||
// Urls routes.
|
||||
Route::resource('/urls', UrlController::class);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue