ldc-qrcollector-back/app/Http/Controllers/Bo/StatsController.php
2026-04-08 15:25:27 +02:00

276 lines
9.1 KiB
PHP

<?php
namespace App\Http\Controllers\Bo;
use App\Enums\PeriodEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StatsRequest;
use App\Lib\Helpers\ChartJsHelper;
use App\Models\Brand;
use App\Models\Url;
use App\Models\UrlStats;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Colors\RandomColor;
use Illuminate\Database\Eloquent\Model;
class StatsController extends Controller
{
/** @var array Colors used in charts */
private $colors = [];
/**
* Display a resource.
*
* @param \App\Http\Requests\Bo\StatsRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function index(StatsRequest $request): \Illuminate\Contracts\View\View
{
/** @var \App\Models\User $user */
$user = auth('backend')->user();
$brandModels = $user->getRelatedBrands();
// Check if it's a post request or not to get data.
if ($request->post()) {
$requestValidated = $request->validated();
$start = $requestValidated['start'] ? Carbon::parse($requestValidated['start']) : null;
$end = $requestValidated['end'] ? Carbon::parse($requestValidated['end']) : null;
$chart = $this->getChart($requestValidated);
}
return view('bo.pages.stats', [
'brandModels' => $brandModels,
'chart' => $chart ?? null,
'start' => $start ?? null,
'end' => $end ?? null,
]);
}
/**
* Get a chart from ChartJs.
*
* @param array $requestValidated
* @return \IcehouseVentures\LaravelChartjs\Builder
*/
private function getChart(array $requestValidated): \IcehouseVentures\LaravelChartjs\Builder
{
$periodInterval = $this->getDateInterval(
$requestValidated['start'],
$requestValidated['end'],
$requestValidated['period'],
);
// Get all labels contained in the interval period.
$labels = [];
foreach ($periodInterval as $date) {
$labels[] = $this->getChartLabels($date, $requestValidated['period']);
}
/**
* Get data from an entity :
* - total of all urls from a brand
* - total of specific url from a brand
* - total of all urls for each brand
*/
$datasets = match (true) {
!$requestValidated['brand'] => $this->buildDatasets(
Brand::all(),
$periodInterval,
$requestValidated['period'],
fn($brand, $date, $period) => $this->getCallsSum($brand, $date, $period),
fn($brand) => $brand->name
),
!$requestValidated['url'] => $this->buildDatasets(
Brand::find($requestValidated['brand'])->urls,
$periodInterval,
$requestValidated['period'],
fn($url, $date, $period) => $this->getCallsSum($url, $date, $period),
fn($url) => $url->value
),
default => $this->buildDatasets(
[Url::find($requestValidated['url'])],
$periodInterval,
$requestValidated['period'],
fn($url, $date, $period) => $this->getCallsSum($url, $date, $period),
fn($url) => $url->value
),
}; //end match
// Get the type of graph when there are several data or only one.
$chartType = count(Arr::first($datasets)['data'] ?? []) > 1 ? 'line' : 'bar';
return (new ChartJsHelper())
->name('stats')
->type($chartType)
->labels($labels)
->datasets($datasets);
}
/**
* Get data from an entity.
*
* @param iterable $entities
* @param \Carbon\CarbonPeriod $periodInterval
* @param string $period
* @param callable $getStats
* @param callable $getLabel
* @return array
*/
private function buildDatasets(
iterable $entities,
CarbonPeriod $periodInterval,
string $period,
callable $getStats,
callable $getLabel
): array {
$datasets = [];
foreach ($entities as $entity) {
$data = [];
foreach ($periodInterval as $date) {
$data[] = $getStats($entity, $date, $period);
}
$color = $this->getUniqueColor();
$datasets[] = [
'label' => $getLabel($entity),
'backgroundColor' => $color,
'borderColor' => $color,
'pointBorderColor' => $color,
'pointBackgroundColor' => $color,
'data' => $data,
'lineTension' => 0.4,
];
}
return $datasets;
}
/**
* Get the sum of all calls.
*
* @param \Illuminate\Database\Eloquent\Model $entity
* @param \Carbon\Carbon $date
* @param string $period
* @return integer
*/
private function getCallsSum(Model $entity, Carbon $date, string $period): int
{
$urlIds = method_exists($entity, 'urls') && $entity->urls()->exists()
? $entity->urls()->pluck('id')
: collect([$entity->getKey()]);
return UrlStats::query()
->when(
$period === PeriodEnum::day->name,
fn($query) => $query->whereDate('date', $date)
)
->when(
$period === PeriodEnum::week->name,
fn($query) => $query->whereBetween('date', [$date->copy()->startOfWeek(), $date->copy()->endOfWeek()])
)
->when(
$period === PeriodEnum::month->name,
fn($query) => $query->whereBetween('date', [$date->copy()->startOfMonth(), $date->copy()->endOfMonth()])
)
->whereIn('url_id', $urlIds)
->sum('calls');
}
/**
* 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): \Carbon\CarbonPeriod
{
$startDate = Carbon::parse($start);
$endDate = Carbon::parse($end);
/**
* Set config for each enum names.
* methods: specific methods needed.
* interval: specific interval defined.
*/
$unitMap = [
$this->getEnumName(PeriodEnum::day) => [
'methods' => null,
'interval' => null
],
$this->getEnumName(PeriodEnum::week) => [
'methods' => ['startOfWeek', 'endOfWeek'],
'interval' => '1 week'
],
$this->getEnumName(PeriodEnum::month) => [
'methods' => ['startOfMonth', 'endOfMonth'],
'interval' => '1 month'
],
];
// Check if config exist for the given period, get the day config by default.
$config = $unitMap[$period] ?? $unitMap[$this->getEnumName(PeriodEnum::day)];
// Execute configuration methods where available.
if ($config['methods']) {
[$startMethod, $endMethod] = $config['methods'];
$startDate = $startDate->$startMethod();
$endDate = $endDate->$endMethod();
}
return CarbonPeriod::create($startDate, $config['interval'], $endDate);
}
/**
* Hack to get the name of a php 7.3 enum.
*
* @param \App\Enums\PeriodEnum $enum
* @return string
*/
private function getEnumName(PeriodEnum $enum): string
{
/** @var array $enumFlip */
$enumFlip = array_flip(PeriodEnum::toValues());
return $enumFlip[$enum->value];
}
/**
* Get labels for charts.
*
* @param \Carbon\Carbon $date
* @param string $period Can be day|week|month.
* @return array
*/
private function getChartLabels(Carbon $date, string $period): array
{
// Match given period with enum names.
return match ($period) {
$this->getEnumName(PeriodEnum::week) => array_unique([sprintf(
'Du %s au %s',
$date->startOfWeek()->format('Y-m-d'),
$date->endOfWeek()->format('Y-m-d')
)]),
$this->getEnumName(PeriodEnum::month) => array_unique([
Str::ucfirst($date->monthName)
]),
default => array_unique([$date->format('d-m-Y')]),
};
}
/**
* Get unique color.
*
* @return string
*/
private function getUniqueColor(): string
{
do {
$color = RandomColor::one();
// Check if color is already used.
} while (in_array($color, $this->colors));
$this->colors[] = $color;
return $color;
}
}