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 \App\Lib\Helpers\ChartJsHelper */ private function getChart(array $requestValidated): \App\Lib\Helpers\ChartJsHelper { $periodInterval = $this->getDateInterval( $requestValidated['start'], $requestValidated['end'], $requestValidated['period'], ); // Get all labels contained in the interval period. 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 */ if (!$requestValidated['brand']) { $datasets = $this->buildDatasets( Brand::all(), $periodInterval, $requestValidated['period'], function ($brand, $date, $period) { return $this->getCallsSum($brand, $date, $period); }, function ($brand) { return $brand->name; } ); } elseif (!$requestValidated['url']) { $datasets = $this->buildDatasets( Brand::find($requestValidated['brand'])->urls, $periodInterval, $requestValidated['period'], function ($url, $date, $period) { return $this->getCallsSum($url, $date, $period); }, function ($url) { return $url->value; } ); } else { $datasets = $this->buildDatasets( [Url::find($requestValidated['url'])], $periodInterval, $requestValidated['period'], function ($url, $date, $period) { return $this->getCallsSum($url, $date, $period); }, function ($url) { return $url->value; } ); } // 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 { 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->pluck('id') : collect([$entity->getKey()]); return UrlStats::query() ->when( $period === PeriodEnum::day()->value, function ($query) use ($date) { return $query->whereDate('date', $date); } ) ->when( $period === PeriodEnum::week()->value, function ($query) use ($date) { return $query->whereBetween('date', [$date->copy()->startOfWeek(), $date->copy()->endOfWeek()]); } ) ->when( $period === PeriodEnum::month()->value, function ($query) use ($date) { return $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::values()); 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. switch ($period) { case $this->getEnumName(PeriodEnum::week()): return array_unique([sprintf( 'Du %s au %s', $date->startOfWeek()->format('Y-m-d'), $date->endOfWeek()->format('Y-m-d') )]); case $this->getEnumName(PeriodEnum::month()): return array_unique([ Str::ucfirst($date->monthName) ]); default: return 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; } }