From 4dadd77dee77f410353f8ea4e06494f8bbaa9ca1 Mon Sep 17 00:00:00 2001 From: Rauno Tegelmann Date: Fri, 6 Sep 2024 10:15:09 +0300 Subject: [PATCH] feat(dashboard): highlight and always show logged in users rank on user's page (#243) Co-authored-by: Dario Portmann Co-authored-by: Dario Portmann <33952107+daerup@users.noreply.github.com> Resolves #224 --- src/app/dashboard/[slug]/page.tsx | 3 +- src/app/dashboard/_components/Dashboard.tsx | 7 +- src/app/dashboard/users/page.tsx | 81 +++++++++++++++++---- src/components/MediaItem/MediaItem.tsx | 21 ++++-- src/components/MediaItem/MediaItemTitle.tsx | 15 +++- src/components/MediaItem/MediaItems.tsx | 3 + src/types/tautulli.d.ts | 1 + src/utils/formatting.ts | 2 +- 8 files changed, 105 insertions(+), 28 deletions(-) diff --git a/src/app/dashboard/[slug]/page.tsx b/src/app/dashboard/[slug]/page.tsx index 49cd2af4..43619121 100644 --- a/src/app/dashboard/[slug]/page.tsx +++ b/src/app/dashboard/[slug]/page.tsx @@ -42,6 +42,7 @@ async function DashboardContent({ params, searchParams }: Props) { } const session = await getServerSession(authOptions) + const loggedInUserId = session?.user.id const period = getPeriod(searchParams, settings) const isPersonal = searchParams.personal === 'true' const sortByPlays = @@ -80,7 +81,7 @@ async function DashboardContent({ params, searchParams }: Props) { serverId={serverId} count={count} settings={settings} - isLoggedIn={!!session} + loggedInUserId={loggedInUserId} /> ) } diff --git a/src/app/dashboard/_components/Dashboard.tsx b/src/app/dashboard/_components/Dashboard.tsx index 5715834d..86c0f9f7 100644 --- a/src/app/dashboard/_components/Dashboard.tsx +++ b/src/app/dashboard/_components/Dashboard.tsx @@ -22,7 +22,7 @@ type Props = { serverId?: string count?: string settings: Settings - isLoggedIn: boolean + loggedInUserId?: string } export default function Dashboard({ @@ -34,8 +34,10 @@ export default function Dashboard({ serverId = '', count, settings, - isLoggedIn, + loggedInUserId, }: Props) { + const isLoggedIn = !!loggedInUserId + function renderFilters(className?: string) { if (type !== 'users') { return ( @@ -98,6 +100,7 @@ export default function Dashboard({ type={type} serverId={serverId} settings={settings} + loggedInUserId={loggedInUserId} /> ) : (
diff --git a/src/app/dashboard/users/page.tsx b/src/app/dashboard/users/page.tsx index 3a4f1cab..7973a767 100644 --- a/src/app/dashboard/users/page.tsx +++ b/src/app/dashboard/users/page.tsx @@ -1,7 +1,7 @@ import { authOptions } from '@/lib/auth' import { DashboardSearchParams } from '@/types/dashboard' import { Settings } from '@/types/settings' -import { TautulliItem, TautulliUser } from '@/types/tautulli' +import { TautulliItem, TautulliItemRow, TautulliUser } from '@/types/tautulli' import { fetchOverseerrStats, fetchOverseerrUserId, @@ -27,14 +27,38 @@ type UserRequestCounts = } | undefined +async function getInactiveUserInTimePeriod( + id: string, +): Promise { + const user = await fetchTautulli('get_user', { + user_id: id, + }) + const nonActive = user?.response?.data ?? ({} as TautulliItemRow) + + nonActive.total_duration = 0 + + return nonActive +} + async function getUsers( + loggedInUserId: string, period: number, requestsPeriod: string, periodString: string, + settings: Settings, ) { + const numberOfUsers = 6 + const allUsersCount = await getUsersCount(settings) + + if (!allUsersCount) { + console.error('[TAUTULLI] - Could not determine the number of users.') + + return + } + const usersRes = await fetchTautulli('get_home_stats', { stat_id: 'top_users', - stats_count: 6, + stats_count: allUsersCount, stats_type: 'duration', time_range: period, }) @@ -44,7 +68,10 @@ async function getUsers( return } - const settings = getSettings() + const isAnonymousAccess = settings.general.isOutsideAccess && !loggedInUserId + const listedUsers = isAnonymousAccess + ? users.slice(0, numberOfUsers) + : await getStatsWithLoggedInUser(loggedInUserId, users, numberOfUsers) const [moviesLib, showsLib, audioLib] = await Promise.all([ getLibrariesByType('movie'), getLibrariesByType('show'), @@ -57,11 +84,9 @@ async function getUsers( if (isOverseerrActive) { const overseerrUserIds = await Promise.all( - users.map(async (user) => { - const overseerrId = await fetchOverseerrUserId(String(user.user_id)) - - return overseerrId - }), + listedUsers.map( + async (user) => await fetchOverseerrUserId(String(user.user_id)), + ), ) usersRequestsCounts = await Promise.all( @@ -81,7 +106,7 @@ async function getUsers( } const usersPlaysAndDurations = await Promise.all( - users.map(async (user) => { + listedUsers.map(async (user) => { let moviesPlaysCount = 0 let showsPlaysCount = 0 let audioPlaysCount = 0 @@ -133,14 +158,37 @@ async function getUsers( }), ) - users.map((user, i) => { + listedUsers.map((user, i) => { user.requests = usersRequestsCounts[i]?.requests || 0 user.movies_plays_count = usersPlaysAndDurations[i].movies_plays_count user.shows_plays_count = usersPlaysAndDurations[i].shows_plays_count user.audio_plays_count = usersPlaysAndDurations[i].audio_plays_count }) - return users + return listedUsers +} + +async function getStatsWithLoggedInUser( + userId: string, + users: TautulliItemRow[], + numberOfUsers: number, +) { + const loggedInUserRank = users.findIndex( + (user) => String(user.user_id) == userId, + ) + const loggedInUser = + users[loggedInUserRank] || (await getInactiveUserInTimePeriod(userId)) + + let slicedUsers = users.slice(0, numberOfUsers) + + if (loggedInUserRank === -1 || loggedInUserRank >= numberOfUsers) { + slicedUsers = users.slice(0, numberOfUsers - 1) + loggedInUser.rank = + loggedInUserRank === -1 ? users.length : loggedInUserRank + slicedUsers.push(loggedInUser) + } + + return slicedUsers } async function getTotalDuration(period: string, settings: Settings) { @@ -207,10 +255,17 @@ async function DashboardUsersContent({ searchParams }: Props) { } const session = await getServerSession(authOptions) + const loggedInUserId = session?.user?.id const period = getPeriod(searchParams, settings) const [usersData, totalDuration, usersCount, totalRequests] = await Promise.all([ - getUsers(period.daysAgo, period.date, period.string), + getUsers( + loggedInUserId, + period.daysAgo, + period.date, + period.string, + settings, + ), getTotalDuration(period.string, settings), getUsersCount(settings), getTotalRequests(period.date, settings), @@ -225,7 +280,7 @@ async function DashboardUsersContent({ searchParams }: Props) { type='users' settings={settings} count={totalRequests} - isLoggedIn={!!session} + loggedInUserId={loggedInUserId} /> ) } diff --git a/src/components/MediaItem/MediaItem.tsx b/src/components/MediaItem/MediaItem.tsx index 54eda20b..c89672ab 100644 --- a/src/components/MediaItem/MediaItem.tsx +++ b/src/components/MediaItem/MediaItem.tsx @@ -29,6 +29,7 @@ type Props = { serverId: string activeStats: string[] settings: Settings + loggedInUserId?: string } export default function MediaItem({ @@ -38,14 +39,16 @@ export default function MediaItem({ serverId, activeStats, settings, + loggedInUserId, }: Props) { const tautulliUrl = settings.connection.tautulliUrl const isTmdbPoster = data.thumb?.startsWith('https://image.tmdb.org') + const isUserDashboard = type === 'users' const posterSrc = isTmdbPoster ? data.thumb : `/api/image?url=${encodeURIComponent( `${tautulliUrl}/pms_image_proxy?img=${ - type === 'users' ? data.user_thumb : data.thumb + isUserDashboard ? data.user_thumb : data.thumb }&width=300`, )}` const [dataKey, setDataKey] = useState(0) @@ -62,7 +65,10 @@ export default function MediaItem({ return ( 4 && 'hidden lg:flex')} + className={clsx( + 'flex gap-3 2xl:items-center', + i > 4 && !isUserDashboard && 'hidden lg:flex', + )} variants={slideDown} initial='hidden' animate='show' @@ -72,9 +78,7 @@ export default function MediaItem({ { setImageSrc(placeholderSvg)} @@ -83,10 +87,11 @@ export default function MediaItem({
{(type === 'movie' || type === 'show') && (
@@ -149,7 +154,7 @@ export default function MediaItem({ )} {/* Plays */} {activeStats.includes('plays') && - (type === 'users' ? ( + (isUserDashboard ? ( <> {data.shows_plays_count > 0 && (
  • @@ -191,7 +196,7 @@ export default function MediaItem({ )} {/* Requests */} {activeStats.includes('requests') && - type === 'users' && + isUserDashboard && data.requests > 0 && (
  • diff --git a/src/components/MediaItem/MediaItemTitle.tsx b/src/components/MediaItem/MediaItemTitle.tsx index 8791e120..e3d006bc 100644 --- a/src/components/MediaItem/MediaItemTitle.tsx +++ b/src/components/MediaItem/MediaItemTitle.tsx @@ -11,14 +11,23 @@ type Props = { data: TautulliItemRow type: string parentRef: RefObject + loggedInUserId?: string } const topColors = ['text-yellow-300', 'text-gray-300', 'text-yellow-700'] -export default function MediaItemTitle({ i, data, type, parentRef }: Props) { +export default function MediaItemTitle({ + i, + data, + type, + parentRef, + loggedInUserId, +}: Props) { const titleRef = useRef(null) const numberRef = useRef(null) + const isLoggedIn = String(data.user_id) === loggedInUserId const controls = useAnimation() + const isUsersDashboard = type === 'users' useEffect(() => { function checkWidth() { @@ -76,7 +85,7 @@ export default function MediaItemTitle({ i, data, type, parentRef }: Props) { }, [controls, parentRef]) return ( -

    +

    #{i + 1}{' '} @@ -123,7 +132,7 @@ export default function MediaItemTitle({ i, data, type, parentRef }: Props) { animate={controls} ref={titleRef} > - {type === 'users' ? data.friendly_name : data.title} + {isUsersDashboard ? data.friendly_name : data.title}

    diff --git a/src/components/MediaItem/MediaItems.tsx b/src/components/MediaItem/MediaItems.tsx index ae116367..b2186582 100644 --- a/src/components/MediaItem/MediaItems.tsx +++ b/src/components/MediaItem/MediaItems.tsx @@ -9,6 +9,7 @@ type Props = { serverId: string rows?: boolean settings: Settings + loggedInUserId?: string } export default function MediaItems({ @@ -17,6 +18,7 @@ export default function MediaItems({ serverId, rows, settings, + loggedInUserId, }: Props) { return (
      ))}
    diff --git a/src/types/tautulli.d.ts b/src/types/tautulli.d.ts index 1b0964e0..58f185da 100644 --- a/src/types/tautulli.d.ts +++ b/src/types/tautulli.d.ts @@ -50,4 +50,5 @@ export type TautulliItemRow = { movies_plays_count: number shows_plays_count: number user_id: number + rank: number } diff --git a/src/utils/formatting.ts b/src/utils/formatting.ts index 679b0685..6ebe3ed5 100644 --- a/src/utils/formatting.ts +++ b/src/utils/formatting.ts @@ -1,6 +1,6 @@ export function secondsToTime(seconds: number): string { if (seconds <= 0) { - return '' + return '0 mins' } const units = [