From 27f20f1463420718ff38a85a4808e5d5765565e4 Mon Sep 17 00:00:00 2001
From: Ilya Stepenko <38462532+VampireAotD@users.noreply.github.com>
Date: Sun, 15 Dec 2024 15:49:19 +0200
Subject: [PATCH] Added anime list statistic
---
.../Controllers/Anime/AnimeController.php | 10 +++-
.../Services/User/UserAnimeListService.php | 48 ++++++++++++++++
.../js/entities/anime-list/model/index.ts | 8 ++-
.../js/entities/anime-list/model/types.ts | 16 +++++-
.../list-statistic/AnimeListStatistic.vue | 56 +++++++++++++++++++
.../js/features/anime/list-statistic/index.ts | 1 +
src/resources/js/pages/Anime/Show.vue | 24 +++++---
src/resources/js/pages/Invitation/Index.vue | 4 +-
.../js/shared/ui/hover-card/HoverCard.vue | 19 +++++++
.../shared/ui/hover-card/HoverCardContent.vue | 44 +++++++++++++++
.../shared/ui/hover-card/HoverCardTrigger.vue | 11 ++++
.../js/shared/ui/hover-card/index.ts | 3 +
.../js/shared/ui/progress/Progress.vue | 38 +++++++++++++
src/resources/js/shared/ui/progress/index.ts | 1 +
14 files changed, 268 insertions(+), 15 deletions(-)
create mode 100644 src/resources/js/features/anime/list-statistic/AnimeListStatistic.vue
create mode 100644 src/resources/js/features/anime/list-statistic/index.ts
create mode 100644 src/resources/js/shared/ui/hover-card/HoverCard.vue
create mode 100644 src/resources/js/shared/ui/hover-card/HoverCardContent.vue
create mode 100644 src/resources/js/shared/ui/hover-card/HoverCardTrigger.vue
create mode 100644 src/resources/js/shared/ui/hover-card/index.ts
create mode 100644 src/resources/js/shared/ui/progress/Progress.vue
create mode 100644 src/resources/js/shared/ui/progress/index.ts
diff --git a/src/app/Http/Controllers/Anime/AnimeController.php b/src/app/Http/Controllers/Anime/AnimeController.php
index 477d358..eb6b6c3 100644
--- a/src/app/Http/Controllers/Anime/AnimeController.php
+++ b/src/app/Http/Controllers/Anime/AnimeController.php
@@ -93,10 +93,14 @@ public function show(Request $request, Anime $anime): Response
'genres:name',
]);
- $animeListEntry = $this->userAnimeListService->findById($request->user(), $anime->id);
- $animeListStatuses = AnimeListStatusEnum::cases();
+ $animeListEntry = $this->userAnimeListService->findById($request->user(), $anime->id);
+ $animeListStatuses = AnimeListStatusEnum::cases();
+ $animeListStatistic = $this->userAnimeListService->animeStatistics($anime->id);
- return Inertia::render('Anime/Show', compact('anime', 'animeListEntry', 'animeListStatuses'));
+ return Inertia::render(
+ 'Anime/Show',
+ compact('anime', 'animeListEntry', 'animeListStatuses', 'animeListStatistic')
+ );
}
/**
diff --git a/src/app/Services/User/UserAnimeListService.php b/src/app/Services/User/UserAnimeListService.php
index eb2f266..7ac2c0d 100644
--- a/src/app/Services/User/UserAnimeListService.php
+++ b/src/app/Services/User/UserAnimeListService.php
@@ -8,6 +8,7 @@
use App\Enums\UserAnimeList\StatusEnum;
use App\Models\Pivots\UserAnimeList;
use App\Models\User;
+use Illuminate\Support\Facades\DB;
final class UserAnimeListService
{
@@ -17,6 +18,53 @@ public function findById(User $user, string $animeId): ?UserAnimeList
return $user->animeList()->where('anime_id', $animeId)->first()?->pivot;
}
+ /**
+ * @return array{status: string, user_count: int, percentage: float}
+ */
+ public function animeStatistics(string $animeId): array
+ {
+ $subQuery = UserAnimeList::query()->fromRaw(
+ "(
+ SELECT 'plan_to_watch' AS status
+ UNION ALL SELECT 'watching'
+ UNION ALL SELECT 'on_hold'
+ UNION ALL SELECT 'completed'
+ UNION ALL SELECT 'dropped'
+ ) as statuses"
+ );
+
+ // Not using model directly because of Model::shouldBeStrict()
+ return DB::table(UserAnimeList::class)
+ ->fromSub(
+ $subQuery->leftJoin(
+ 'user_anime_list as ual',
+ static fn($join) => $join->on(
+ 'statuses.status',
+ '=',
+ 'ual.status'
+ )->where('ual.anime_id', $animeId)
+ )
+ ->groupBy('statuses.status')
+ ->select([
+ 'statuses.status',
+ DB::raw('COUNT(ual.user_id) as user_count'),
+ DB::raw('SUM(COUNT(ual.user_id)) OVER() as total_count'),
+ ]),
+ 'sub'
+ )
+ ->select([
+ 'status',
+ 'user_count',
+ DB::raw('ROUND(user_count * 100 / total_count, 1) as percentage'),
+ ])
+ ->get()
+ ->map(static function (\stdClass $item) {
+ $item->percentage = (float) $item->percentage;
+ return (array) $item;
+ })
+ ->toArray();
+ }
+
public function addAnime(User $user, string $animeId): void
{
$user->animeList()->attach($animeId, ['status' => StatusEnum::PLAN_TO_WATCH]);
diff --git a/src/resources/js/entities/anime-list/model/index.ts b/src/resources/js/entities/anime-list/model/index.ts
index 8022df1..2aa6b88 100644
--- a/src/resources/js/entities/anime-list/model/index.ts
+++ b/src/resources/js/entities/anime-list/model/index.ts
@@ -1 +1,7 @@
-export { Status, type AnimeList, type AnimeListEntry } from './types';
+export {
+ Status,
+ type AnimeList,
+ type AnimeListEntry,
+ type AnimeListEntryStatistic,
+ type AnimeListStatistics,
+} from './types';
diff --git a/src/resources/js/entities/anime-list/model/types.ts b/src/resources/js/entities/anime-list/model/types.ts
index baea627..628729e 100644
--- a/src/resources/js/entities/anime-list/model/types.ts
+++ b/src/resources/js/entities/anime-list/model/types.ts
@@ -14,4 +14,18 @@ type AnimeListEntry = {
type AnimeList = AnimeListEntry[];
-export { Status, type AnimeListEntry, type AnimeList };
+type AnimeListEntryStatistic = {
+ status: Status;
+ user_count: number;
+ percentage: number;
+};
+
+type AnimeListStatistics = AnimeListEntryStatistic[];
+
+export {
+ Status,
+ type AnimeListEntry,
+ type AnimeList,
+ type AnimeListEntryStatistic,
+ type AnimeListStatistics,
+};
diff --git a/src/resources/js/features/anime/list-statistic/AnimeListStatistic.vue b/src/resources/js/features/anime/list-statistic/AnimeListStatistic.vue
new file mode 100644
index 0000000..3fff24b
--- /dev/null
+++ b/src/resources/js/features/anime/list-statistic/AnimeListStatistic.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+