196
resources/client/admin/reports/top-models-chart-layout.tsx
Executable file
196
resources/client/admin/reports/top-models-chart-layout.tsx
Executable file
@@ -0,0 +1,196 @@
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {ChartLayout, ChartLayoutProps} from '@common/charts/chart-layout';
|
||||
import React, {Fragment, ReactElement} from 'react';
|
||||
import {ReportMetric} from '@common/admin/analytics/report-metric';
|
||||
import {ChartLoadingIndicator} from '@common/charts/chart-loading-indicator';
|
||||
import {TopModelDatasetItem} from '@app/admin/reports/requests/use-insights-report';
|
||||
import {InfoIcon} from '@common/icons/material/Info';
|
||||
import {FormattedNumber} from '@common/i18n/formatted-number';
|
||||
import {Link} from 'react-router-dom';
|
||||
import {UserAvatar} from '@common/ui/images/user-avatar';
|
||||
import {TitlePoster} from '@app/titles/title-poster/title-poster';
|
||||
import {TitleLink} from '@app/titles/title-link';
|
||||
import {UserProfileLink} from '@common/users/user-profile-link';
|
||||
import {MediaPlayIcon} from '@common/icons/media/media-play';
|
||||
import {getWatchLink} from '@app/videos/watch-page/get-watch-link';
|
||||
import {SeasonPoster} from '@app/seasons/season-poster';
|
||||
import {SeasonLink} from '@app/seasons/season-link';
|
||||
import {EpisodePoster} from '@app/episodes/episode-poster/episode-poster';
|
||||
import {EpisodeLink} from '@app/episodes/episode-link';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface Props extends Partial<ChartLayoutProps> {
|
||||
data?: ReportMetric<TopModelDatasetItem>;
|
||||
title: ReactElement;
|
||||
}
|
||||
export function TopModelsChartLayout({data, isLoading, ...layoutProps}: Props) {
|
||||
const dataItems = data?.datasets[0].data || [];
|
||||
|
||||
return (
|
||||
<ChartLayout
|
||||
{...layoutProps}
|
||||
className="w-1/2 min-w-500 md:min-w-0"
|
||||
contentIsFlex={isLoading}
|
||||
contentClassName="max-h-[370px] overflow-y-auto compact-scrollbar"
|
||||
>
|
||||
{isLoading && <ChartLoadingIndicator />}
|
||||
{dataItems.map(item => (
|
||||
<div
|
||||
key={item.model.id}
|
||||
className="mb-20 flex items-center justify-between gap-24 text-sm"
|
||||
>
|
||||
<div className="flex items-center gap-8">
|
||||
<Image
|
||||
model={item.model}
|
||||
size="w-42 h-42"
|
||||
className="flex-shrink-0 rounded"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm">
|
||||
<Name model={item.model} />
|
||||
</div>
|
||||
<div className="text-xs text-muted">
|
||||
<Description model={item.model} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-4">
|
||||
<MediaPlayIcon className="text-muted" size="sm" />
|
||||
<Trans
|
||||
message=":count plays"
|
||||
values={{count: <FormattedNumber value={item.value} />}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{!isLoading && !dataItems.length ? (
|
||||
<div className="flex items-center gap-8 text-muted">
|
||||
<InfoIcon size="sm" />
|
||||
<Trans message="No plays in selected timeframe." />
|
||||
</div>
|
||||
) : null}
|
||||
</ChartLayout>
|
||||
);
|
||||
}
|
||||
|
||||
interface ImageProps {
|
||||
model: TopModelDatasetItem['model'];
|
||||
size: string;
|
||||
className: string;
|
||||
}
|
||||
function Image({model, size, className}: ImageProps) {
|
||||
const link = `/admin/${model.model_type}s/${model.id}`;
|
||||
|
||||
switch (model.model_type) {
|
||||
case 'title':
|
||||
return (
|
||||
<TitlePoster
|
||||
title={model}
|
||||
size={size}
|
||||
srcSize="sm"
|
||||
className={className}
|
||||
link={`/admin/titles/${model.id}/insights`}
|
||||
/>
|
||||
);
|
||||
case 'season':
|
||||
return (
|
||||
<SeasonPoster
|
||||
season={model}
|
||||
title={model.title!}
|
||||
size={size}
|
||||
srcSize="sm"
|
||||
className={className}
|
||||
link={`/admin/titles/${model.title_id}/insights/seasons/${model.number}`}
|
||||
/>
|
||||
);
|
||||
case 'episode':
|
||||
return (
|
||||
<EpisodePoster
|
||||
episode={model}
|
||||
title={model.title!}
|
||||
size={size}
|
||||
srcSize="sm"
|
||||
className={className}
|
||||
link={`/admin/titles/${model.title_id}/insights/seasons/${model.season_number}/episodes/${model.episode_number}`}
|
||||
/>
|
||||
);
|
||||
case 'video':
|
||||
return model.thumbnail ? (
|
||||
<Link to={link} className={clsx(size, className)}>
|
||||
<img src={model.thumbnail} className="h-full w-full" alt="" />
|
||||
</Link>
|
||||
) : (
|
||||
<TitlePoster
|
||||
title={model.title!}
|
||||
size={size}
|
||||
srcSize="sm"
|
||||
className={className}
|
||||
link={`/admin/videos/${model.id}/insights`}
|
||||
/>
|
||||
);
|
||||
case 'user':
|
||||
// there's no separate insights page for user
|
||||
return <UserAvatar user={model} size={size} className={className} />;
|
||||
}
|
||||
}
|
||||
|
||||
interface NameProps {
|
||||
model: TopModelDatasetItem['model'];
|
||||
}
|
||||
function Name({model}: NameProps) {
|
||||
switch (model.model_type) {
|
||||
case 'title':
|
||||
return <TitleLink title={model} target="_blank" />;
|
||||
case 'season':
|
||||
return (
|
||||
<SeasonLink
|
||||
title={model.title!}
|
||||
seasonNumber={model.number}
|
||||
target="_blank"
|
||||
/>
|
||||
);
|
||||
case 'episode':
|
||||
return (
|
||||
<EpisodeLink
|
||||
title={model.title!}
|
||||
episode={model}
|
||||
seasonNumber={model.season_number}
|
||||
target="_blank"
|
||||
/>
|
||||
);
|
||||
case 'video':
|
||||
return (
|
||||
<Link
|
||||
to={getWatchLink(model)}
|
||||
className="hover:underline"
|
||||
target="_blank"
|
||||
>
|
||||
{model.name}
|
||||
</Link>
|
||||
);
|
||||
case 'user':
|
||||
return model.id ? (
|
||||
<UserProfileLink user={model} target="_blank" />
|
||||
) : (
|
||||
<Fragment>{model.display_name}</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface DescriptionProps {
|
||||
model: TopModelDatasetItem['model'];
|
||||
}
|
||||
function Description({model}: DescriptionProps) {
|
||||
switch (model.model_type) {
|
||||
case 'title':
|
||||
return <span>{model.year}</span>;
|
||||
case 'season':
|
||||
return <TitleLink title={model.title!} target="_blank" />;
|
||||
case 'episode':
|
||||
return <TitleLink title={model.title!} target="_blank" />;
|
||||
case 'user':
|
||||
return null;
|
||||
case 'video':
|
||||
return <TitleLink title={model.title!} target="_blank" />;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user