dishplanner/src/DishPlanner/Dashboard/Services/DashboardStatsService.php

115 lines
3.6 KiB
PHP
Raw Normal View History

<?php
namespace DishPlanner\Dashboard\Services;
use App\Models\Dish;
use App\Models\Planner;
use App\Models\ScheduledUserDish;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;
class DashboardStatsService
{
public function __construct(private readonly Planner $planner) {}
/**
* @return array{
* dish_count: int,
* user_count: int,
* meals_this_month: int,
* favorite_dishes: Collection<int, array{user: User, dish: Dish|null, count: int}>
* }
*/
public function stats(): array
{
// PHPStan cannot equate the identical array shapes; see favoriteDishes().
/** @phpstan-ignore return.type */
return [
'dish_count' => $this->dishCount(),
'user_count' => $this->userCount(),
'meals_this_month' => $this->mealsThisMonth(),
'favorite_dishes' => $this->favoriteDishes(),
];
}
public function dishCount(): int
{
return Dish::where('planner_id', $this->planner->id)->count();
}
public function userCount(): int
{
return User::where('planner_id', $this->planner->id)->count();
}
public function mealsThisMonth(): int
{
$start = Carbon::now()->startOfMonth()->toDateString();
$end = Carbon::now()->endOfMonth()->toDateString();
return ScheduledUserDish::query()
->where('is_skipped', false)
->whereNotNull('user_dish_id')
->whereHas('schedule', fn ($query) => $query
->where('planner_id', $this->planner->id)
->where('is_skipped', false)
->whereBetween('date', [$start, $end]))
->count();
}
/**
* @return Collection<int, array{user: User, dish: Dish|null, count: int}>
*/
public function favoriteDishes(): Collection
{
$users = User::where('planner_id', $this->planner->id)
->orderBy('name')
->get();
$favorites = ScheduledUserDish::query()
->with(['user', 'userDish.dish'])
->whereNotNull('user_dish_id')
->whereHas('schedule', fn ($query) => $query->where('planner_id', $this->planner->id))
->get()
->groupBy('user_id')
->map(fn (Collection $items) => $this->favoriteForItems($items));
// Eloquent map() returns a union type PHPStan cannot narrow to the declared shape.
/** @phpstan-ignore return.type */
return $users->map(fn (User $user) => $favorites->get($user->id) ?? [
'user' => $user,
'dish' => null,
'count' => 0,
])->toBase()->values();
}
/**
* @param Collection<int, ScheduledUserDish> $items
* @return array{user: User, dish: Dish|null, count: int}
*/
private function favoriteForItems(Collection $items): array
{
$dishCounts = $items
->groupBy(fn (ScheduledUserDish $item) => $item->userDish->dish_id)
->map(fn (Collection $dishItems) => [
'dish' => $dishItems->firstOrFail()->userDish->dish,
'count' => $dishItems->count(),
]);
$top = $dishCounts->sort(function (array $a, array $b) {
if ($a['count'] === $b['count']) {
return ($a['dish']->name ?? '') <=> ($b['dish']->name ?? '');
}
return $b['count'] <=> $a['count'];
})->first();
return [
'user' => $items->firstOrFail()->user,
'dish' => $top['dish'] ?? null,
'count' => $top['count'] ?? 0,
];
}
}