From 61734e752f6c0502e5517c416887bc4e06ee9427 Mon Sep 17 00:00:00 2001 From: olexh Date: Fri, 29 Mar 2024 16:45:23 +0200 Subject: [PATCH] add score stats to anime page --- .../[slug]/(animeDetails)/characters/page.tsx | 2 +- .../[slug]/(animeDetails)/franchise/page.tsx | 2 +- .../[slug]/(animeDetails)/links/page.tsx | 2 +- .../[slug]/(animeDetails)/media/page.tsx | 2 +- .../[slug]/(animeDetails)/staff/page.tsx | 2 +- .../[slug]/_components/watchlist-stats.tsx | 95 ------------------- .../{_components => components}/about.tsx | 0 .../actions/actions.tsx | 2 +- .../actions/components}/watch-stats.tsx | 0 .../actions/index.ts | 0 .../characters.tsx | 0 .../{_components => components}/cover.tsx | 0 .../description.tsx | 0 .../{_components => components}/franchise.tsx | 0 .../{_components => components}/links.tsx | 0 .../{_components => components}/media.tsx | 0 .../{_components => components}/staff.tsx | 0 .../{_components => components}/title.tsx | 0 .../watch-stats/components/score.tsx | 54 +++++++++++ .../watch-stats/components/ui/stats.tsx | 71 ++++++++++++++ .../watch-stats/components/watchlist.tsx | 51 ++++++++++ .../components/watch-stats/watch-stats.tsx | 39 ++++++++ app/(pages)/anime/[slug]/layout.tsx | 6 +- app/(pages)/anime/[slug]/page.tsx | 20 ++-- .../schedule/components/ui/schedule-item.tsx | 40 +++++--- types/hikka.d.ts | 7 ++ 26 files changed, 266 insertions(+), 129 deletions(-) delete mode 100644 app/(pages)/anime/[slug]/_components/watchlist-stats.tsx rename app/(pages)/anime/[slug]/{_components => components}/about.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/actions/actions.tsx (87%) rename app/(pages)/anime/[slug]/{_components/actions/_components => components/actions/components}/watch-stats.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/actions/index.ts (100%) rename app/(pages)/anime/[slug]/{_components => components}/characters.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/cover.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/description.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/franchise.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/links.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/media.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/staff.tsx (100%) rename app/(pages)/anime/[slug]/{_components => components}/title.tsx (100%) create mode 100644 app/(pages)/anime/[slug]/components/watch-stats/components/score.tsx create mode 100644 app/(pages)/anime/[slug]/components/watch-stats/components/ui/stats.tsx create mode 100644 app/(pages)/anime/[slug]/components/watch-stats/components/watchlist.tsx create mode 100644 app/(pages)/anime/[slug]/components/watch-stats/watch-stats.tsx diff --git a/app/(pages)/anime/[slug]/(animeDetails)/characters/page.tsx b/app/(pages)/anime/[slug]/(animeDetails)/characters/page.tsx index 226d0d77..bfaeb4dc 100644 --- a/app/(pages)/anime/[slug]/(animeDetails)/characters/page.tsx +++ b/app/(pages)/anime/[slug]/(animeDetails)/characters/page.tsx @@ -1,6 +1,6 @@ import { Metadata, ResolvingMetadata } from 'next'; -import Characters from '../../_components/characters'; +import Characters from '@/app/(pages)/anime/[slug]/components/characters'; export async function generateMetadata( { params }: { params: { slug: string } }, diff --git a/app/(pages)/anime/[slug]/(animeDetails)/franchise/page.tsx b/app/(pages)/anime/[slug]/(animeDetails)/franchise/page.tsx index 92927205..ea2924fa 100644 --- a/app/(pages)/anime/[slug]/(animeDetails)/franchise/page.tsx +++ b/app/(pages)/anime/[slug]/(animeDetails)/franchise/page.tsx @@ -1,6 +1,6 @@ import { Metadata, ResolvingMetadata } from 'next'; -import Franchise from '../../_components/franchise'; +import Franchise from '@/app/(pages)/anime/[slug]/components/franchise'; export async function generateMetadata( { params }: { params: { slug: string } }, diff --git a/app/(pages)/anime/[slug]/(animeDetails)/links/page.tsx b/app/(pages)/anime/[slug]/(animeDetails)/links/page.tsx index 5e1c924f..40684bed 100644 --- a/app/(pages)/anime/[slug]/(animeDetails)/links/page.tsx +++ b/app/(pages)/anime/[slug]/(animeDetails)/links/page.tsx @@ -1,6 +1,6 @@ import { Metadata, ResolvingMetadata } from 'next'; -import Links from '../../_components/links'; +import Links from '@/app/(pages)/anime/[slug]/components/links'; export async function generateMetadata( { params }: { params: { slug: string } }, diff --git a/app/(pages)/anime/[slug]/(animeDetails)/media/page.tsx b/app/(pages)/anime/[slug]/(animeDetails)/media/page.tsx index 3f1638dd..0c4d27ae 100644 --- a/app/(pages)/anime/[slug]/(animeDetails)/media/page.tsx +++ b/app/(pages)/anime/[slug]/(animeDetails)/media/page.tsx @@ -1,6 +1,6 @@ import { Metadata, ResolvingMetadata } from 'next'; -import Media from '../../_components/media'; +import Media from '@/app/(pages)/anime/[slug]/components/media'; export async function generateMetadata( { params }: { params: { slug: string } }, diff --git a/app/(pages)/anime/[slug]/(animeDetails)/staff/page.tsx b/app/(pages)/anime/[slug]/(animeDetails)/staff/page.tsx index 3ca399e8..f2a4d968 100644 --- a/app/(pages)/anime/[slug]/(animeDetails)/staff/page.tsx +++ b/app/(pages)/anime/[slug]/(animeDetails)/staff/page.tsx @@ -1,6 +1,6 @@ import { Metadata, ResolvingMetadata } from 'next'; -import Staff from '../../_components/staff'; +import Staff from '@/app/(pages)/anime/[slug]/components/staff'; export async function generateMetadata( { params }: { params: { slug: string } }, diff --git a/app/(pages)/anime/[slug]/_components/watchlist-stats.tsx b/app/(pages)/anime/[slug]/_components/watchlist-stats.tsx deleted file mode 100644 index fc09a377..00000000 --- a/app/(pages)/anime/[slug]/_components/watchlist-stats.tsx +++ /dev/null @@ -1,95 +0,0 @@ -'use client'; - -import { createElement } from 'react'; -import { NumericFormat } from 'react-number-format'; - -import { useParams } from 'next/navigation'; - -import SubHeader from '@/components/sub-header'; -import Small from '@/components/typography/small'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; -import useAnimeInfo from '@/services/hooks/anime/useAnimeInfo'; -import { WATCH_STATUS } from '@/utils/constants'; - - -const WatchlistStats = () => { - const params = useParams(); - const { data } = useAnimeInfo({ slug: String(params.slug) }); - - if (!data) { - return null; - } - - const sumStats = - data.stats.completed + - data.stats.on_hold + - data.stats.dropped + - data.stats.planned + - data.stats.watching; - - return ( -
- -
-
- {Object.keys(data.stats) - .filter((stat) => !stat.includes('score')) - .map((stat) => { - const status = - WATCH_STATUS[stat as API.WatchStatus]; - const percentage = - (100 * data.stats[stat as API.StatType]) / - sumStats; - - return ( - - -
-
-
- {createElement( - status.icon!, - )} -
-
-
-
-
- -
- - - -
- - - {percentage.toFixed(2)}% - - - ); - })} -
-
-
- ); -}; - -export default WatchlistStats; diff --git a/app/(pages)/anime/[slug]/_components/about.tsx b/app/(pages)/anime/[slug]/components/about.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/about.tsx rename to app/(pages)/anime/[slug]/components/about.tsx diff --git a/app/(pages)/anime/[slug]/_components/actions/actions.tsx b/app/(pages)/anime/[slug]/components/actions/actions.tsx similarity index 87% rename from app/(pages)/anime/[slug]/_components/actions/actions.tsx rename to app/(pages)/anime/[slug]/components/actions/actions.tsx index 603f6c96..f3211300 100644 --- a/app/(pages)/anime/[slug]/_components/actions/actions.tsx +++ b/app/(pages)/anime/[slug]/components/actions/actions.tsx @@ -1,7 +1,7 @@ import { getCookie } from '@/app/actions'; import WatchListButton from '@/components/watchlist-button'; -import WatchStats from './_components/watch-stats'; +import WatchStats from '@/app/(pages)/anime/[slug]/components/actions/components/watch-stats'; interface Props { anime?: API.AnimeInfo; diff --git a/app/(pages)/anime/[slug]/_components/actions/_components/watch-stats.tsx b/app/(pages)/anime/[slug]/components/actions/components/watch-stats.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/actions/_components/watch-stats.tsx rename to app/(pages)/anime/[slug]/components/actions/components/watch-stats.tsx diff --git a/app/(pages)/anime/[slug]/_components/actions/index.ts b/app/(pages)/anime/[slug]/components/actions/index.ts similarity index 100% rename from app/(pages)/anime/[slug]/_components/actions/index.ts rename to app/(pages)/anime/[slug]/components/actions/index.ts diff --git a/app/(pages)/anime/[slug]/_components/characters.tsx b/app/(pages)/anime/[slug]/components/characters.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/characters.tsx rename to app/(pages)/anime/[slug]/components/characters.tsx diff --git a/app/(pages)/anime/[slug]/_components/cover.tsx b/app/(pages)/anime/[slug]/components/cover.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/cover.tsx rename to app/(pages)/anime/[slug]/components/cover.tsx diff --git a/app/(pages)/anime/[slug]/_components/description.tsx b/app/(pages)/anime/[slug]/components/description.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/description.tsx rename to app/(pages)/anime/[slug]/components/description.tsx diff --git a/app/(pages)/anime/[slug]/_components/franchise.tsx b/app/(pages)/anime/[slug]/components/franchise.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/franchise.tsx rename to app/(pages)/anime/[slug]/components/franchise.tsx diff --git a/app/(pages)/anime/[slug]/_components/links.tsx b/app/(pages)/anime/[slug]/components/links.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/links.tsx rename to app/(pages)/anime/[slug]/components/links.tsx diff --git a/app/(pages)/anime/[slug]/_components/media.tsx b/app/(pages)/anime/[slug]/components/media.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/media.tsx rename to app/(pages)/anime/[slug]/components/media.tsx diff --git a/app/(pages)/anime/[slug]/_components/staff.tsx b/app/(pages)/anime/[slug]/components/staff.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/staff.tsx rename to app/(pages)/anime/[slug]/components/staff.tsx diff --git a/app/(pages)/anime/[slug]/_components/title.tsx b/app/(pages)/anime/[slug]/components/title.tsx similarity index 100% rename from app/(pages)/anime/[slug]/_components/title.tsx rename to app/(pages)/anime/[slug]/components/title.tsx diff --git a/app/(pages)/anime/[slug]/components/watch-stats/components/score.tsx b/app/(pages)/anime/[slug]/components/watch-stats/components/score.tsx new file mode 100644 index 00000000..3b5ecefa --- /dev/null +++ b/app/(pages)/anime/[slug]/components/watch-stats/components/score.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { useParams } from 'next/navigation'; + +import Small from '@/components/typography/small'; +import useAnimeInfo from '@/services/hooks/anime/useAnimeInfo'; + +import Stats from './ui/stats'; + + +const Score = () => { + const params = useParams(); + const { data } = useAnimeInfo({ slug: String(params.slug) }); + + if (!data) { + return null; + } + + const sumStats = + data.stats.score_1 + + data.stats.score_2 + + data.stats.score_3 + + data.stats.score_4 + + data.stats.score_5 + + data.stats.score_6 + + data.stats.score_7 + + data.stats.score_8 + + data.stats.score_9 + + data.stats.score_10; + + const stats = Object.keys(data.stats) + .reverse() + .reduce((acc: Hikka.WatchStat[], stat) => { + if ( + stat.includes('score') && + data.stats[stat as API.StatType] > 0 + ) { + const percentage = + (100 * data.stats[stat as API.StatType]) / sumStats; + + acc.push({ + icon: {stat.split('score_')[1]}, + percentage, + value: data.stats[stat as API.StatType], + }); + } + + return acc; + }, []); + + return ; +}; + +export default Score; diff --git a/app/(pages)/anime/[slug]/components/watch-stats/components/ui/stats.tsx b/app/(pages)/anime/[slug]/components/watch-stats/components/ui/stats.tsx new file mode 100644 index 00000000..f51bbf2b --- /dev/null +++ b/app/(pages)/anime/[slug]/components/watch-stats/components/ui/stats.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { NumericFormat } from 'react-number-format'; + +import Small from '@/components/typography/small'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; + +interface Props { + stats: Hikka.WatchStat[]; +} + +const Stats = ({ stats }: Props) => { + return ( +
+
+ {stats.map((stat) => { + return ( + + +
+
+ {stat.icon && ( +
+ {stat.icon} +
+ )} +
+
+
+
+
+ + + +
+ + + {stat.percentage.toFixed(2)}% + + + ); + })} +
+
+ ); +}; + +export default Stats; diff --git a/app/(pages)/anime/[slug]/components/watch-stats/components/watchlist.tsx b/app/(pages)/anime/[slug]/components/watch-stats/components/watchlist.tsx new file mode 100644 index 00000000..77e9d963 --- /dev/null +++ b/app/(pages)/anime/[slug]/components/watch-stats/components/watchlist.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { createElement } from 'react'; + +import { useParams } from 'next/navigation'; + +import useAnimeInfo from '@/services/hooks/anime/useAnimeInfo'; +import { WATCH_STATUS } from '@/utils/constants'; + +import Stats from './ui/stats'; + + +const Watchlist = () => { + const params = useParams(); + const { data } = useAnimeInfo({ slug: String(params.slug) }); + + if (!data) { + return null; + } + + const sumStats = + data.stats.completed + + data.stats.on_hold + + data.stats.dropped + + data.stats.planned + + data.stats.watching; + + const stats = Object.keys(data.stats).reduce( + (acc: Hikka.WatchStat[], stat) => { + if (!stat.includes('score')) { + const status = WATCH_STATUS[stat as API.WatchStatus]; + const percentage = + (100 * data.stats[stat as API.StatType]) / sumStats; + + acc.push({ + percentage, + value: data.stats[stat as API.StatType], + icon: status.icon && createElement(status.icon), + color: status.color!, + }); + } + + return acc; + }, + [], + ); + + return ; +}; + +export default Watchlist; diff --git a/app/(pages)/anime/[slug]/components/watch-stats/watch-stats.tsx b/app/(pages)/anime/[slug]/components/watch-stats/watch-stats.tsx new file mode 100644 index 00000000..e9db2d3e --- /dev/null +++ b/app/(pages)/anime/[slug]/components/watch-stats/watch-stats.tsx @@ -0,0 +1,39 @@ +import SubHeader from '@/components/sub-header'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; + +import Score from './components/score'; +import Watchlist from './components/watchlist'; + +const WatchStats = () => { + return ( +
+ + + + MAL + + + + + + У списках + Оцінки + + + + + + + + +
+ ); +}; + +export default WatchStats; diff --git a/app/(pages)/anime/[slug]/layout.tsx b/app/(pages)/anime/[slug]/layout.tsx index a2302722..c7abeed8 100644 --- a/app/(pages)/anime/[slug]/layout.tsx +++ b/app/(pages)/anime/[slug]/layout.tsx @@ -28,9 +28,9 @@ import getDeclensionWord from '@/utils/getDeclensionWord'; import getQueryClient from '@/utils/getQueryClient'; import parseTextFromMarkDown from '@/utils/parseTextFromMarkDown'; -import Actions from './_components/actions'; -import Cover from './_components/cover'; -import Title from './_components/title'; +import Actions from '@/app/(pages)/anime/[slug]/components/actions'; +import Cover from '@/app/(pages)/anime/[slug]/components/cover'; +import Title from '@/app/(pages)/anime/[slug]/components/title'; interface Props extends PropsWithChildren { diff --git a/app/(pages)/anime/[slug]/page.tsx b/app/(pages)/anime/[slug]/page.tsx index ef91b4b2..c316f61b 100644 --- a/app/(pages)/anime/[slug]/page.tsx +++ b/app/(pages)/anime/[slug]/page.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import About from './_components/about'; -import Characters from './_components/characters'; -import Description from './_components/description'; -import Franchise from './_components/franchise'; -import Links from './_components/links'; -import Media from './_components/media'; -import Staff from './_components/staff'; -import WatchListStats from './_components/watchlist-stats'; +import About from './components/about'; +import Characters from './components/characters'; +import Description from './components/description'; +import Franchise from './components/franchise'; +import Links from './components/links'; +import Media from './components/media'; +import Staff from './components/staff'; +import WatchStats from './components/watch-stats/watch-stats'; const AnimePage = () => { return ( @@ -20,13 +20,13 @@ const AnimePage = () => {
- +
- +
diff --git a/app/(pages)/schedule/components/ui/schedule-item.tsx b/app/(pages)/schedule/components/ui/schedule-item.tsx index 5f95c1cc..cbae2320 100644 --- a/app/(pages)/schedule/components/ui/schedule-item.tsx +++ b/app/(pages)/schedule/components/ui/schedule-item.tsx @@ -18,10 +18,11 @@ interface Props { item: API.AnimeSchedule; } -type Tokens = 'xSeconds' | 'xMinutes' | 'xHours' | 'xDays'; +type Tokens = 'xSeconds' | 'xMinutes' | 'xHours' | 'xDays' | 'xMonths'; const formatDistanceLocale = { uk: { + xMonths: '{{count}} міс.', xDays: '{{count}} дн.', xSeconds: '{{count}} сек.', xMinutes: '{{count}} хв.', @@ -64,20 +65,29 @@ const ScheduleItem = ({ item }: Props) => {
-
- {item.time_left > 0 ? formatDuration( - intervalToDuration({ - start: item.airing_at * 1000, - end: Date.now(), - }), - { - format: - item.time_left > 86400 - ? ['days', 'hours'] - : ['hours', 'minutes'], - locale: getShortLocale(), - }, - ) : "Вийшло"} +
+ {item.time_left > 0 + ? formatDuration( + intervalToDuration({ + start: item.airing_at * 1000, + end: Date.now(), + }), + { + format: + item.time_left > 2592000 + ? ['months', 'days'] + : item.time_left > 86400 + ? ['days', 'hours'] + : ['hours', 'minutes'], + locale: getShortLocale(), + }, + ) + : 'Вийшло'}

diff --git a/types/hikka.d.ts b/types/hikka.d.ts index a121c5f1..de90654c 100644 --- a/types/hikka.d.ts +++ b/types/hikka.d.ts @@ -66,5 +66,12 @@ declare global { href: string; seen: boolean; }; + + type WatchStat = { + percentage: number; + value: number; + icon?: ReactNode; + color?: string; + } } }