114 lines
3.6 KiB
PHP
114 lines
3.6 KiB
PHP
<?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,
|
|
];
|
|
}
|
|
}
|