Skip to content

Commit

Permalink
feat(dashboard): highlight and always show logged in users rank on us…
Browse files Browse the repository at this point in the history
…er's page (#243)

Co-authored-by: Dario Portmann <daerup@gmx.ch>
Co-authored-by: Dario Portmann <33952107+daerup@users.noreply.github.com>

Resolves #224
  • Loading branch information
RaunoT authored Sep 6, 2024
1 parent 254ac86 commit 4dadd77
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 28 deletions.
3 changes: 2 additions & 1 deletion src/app/dashboard/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -80,7 +81,7 @@ async function DashboardContent({ params, searchParams }: Props) {
serverId={serverId}
count={count}
settings={settings}
isLoggedIn={!!session}
loggedInUserId={loggedInUserId}
/>
)
}
Expand Down
7 changes: 5 additions & 2 deletions src/app/dashboard/_components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Props = {
serverId?: string
count?: string
settings: Settings
isLoggedIn: boolean
loggedInUserId?: string
}

export default function Dashboard({
Expand All @@ -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 (
Expand Down Expand Up @@ -98,6 +100,7 @@ export default function Dashboard({
type={type}
serverId={serverId}
settings={settings}
loggedInUserId={loggedInUserId}
/>
) : (
<div className='flex flex-1 flex-col justify-center text-center text-neutral-300'>
Expand Down
81 changes: 68 additions & 13 deletions src/app/dashboard/users/page.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -27,14 +27,38 @@ type UserRequestCounts =
}
| undefined

async function getInactiveUserInTimePeriod(
id: string,
): Promise<TautulliItemRow> {
const user = await fetchTautulli<TautulliItemRow>('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<TautulliItem>('get_home_stats', {
stat_id: 'top_users',
stats_count: 6,
stats_count: allUsersCount,
stats_type: 'duration',
time_range: period,
})
Expand All @@ -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'),
Expand All @@ -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(
Expand 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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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),
Expand All @@ -225,7 +280,7 @@ async function DashboardUsersContent({ searchParams }: Props) {
type='users'
settings={settings}
count={totalRequests}
isLoggedIn={!!session}
loggedInUserId={loggedInUserId}
/>
)
}
Expand Down
21 changes: 13 additions & 8 deletions src/components/MediaItem/MediaItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Props = {
serverId: string
activeStats: string[]
settings: Settings
loggedInUserId?: string
}

export default function MediaItem({
Expand All @@ -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<number>(0)
Expand All @@ -62,7 +65,10 @@ export default function MediaItem({
return (
<motion.li
key={dataKey}
className={clsx('flex gap-3 2xl:items-center', i > 4 && 'hidden lg:flex')}
className={clsx(
'flex gap-3 2xl:items-center',
i > 4 && !isUserDashboard && 'hidden lg:flex',
)}
variants={slideDown}
initial='hidden'
animate='show'
Expand All @@ -72,9 +78,7 @@ export default function MediaItem({
<Image
fill
className='object-cover object-top'
alt={
type === 'users' ? data.user + ' avatar' : data.title + ' poster'
}
alt={isUserDashboard ? data.user + ' avatar' : data.title + ' poster'}
src={imageSrc}
sizes='10rem'
onError={() => setImageSrc(placeholderSvg)}
Expand All @@ -83,10 +87,11 @@ export default function MediaItem({
</div>
<div className='overflow-hidden' ref={titleContainerRef}>
<MediaItemTitle
i={i}
i={data.rank || i}
data={data}
type={type}
parentRef={titleContainerRef}
loggedInUserId={loggedInUserId}
/>
{(type === 'movie' || type === 'show') && (
<div className='relative z-10 mb-3 flex items-center gap-2'>
Expand Down Expand Up @@ -149,7 +154,7 @@ export default function MediaItem({
)}
{/* Plays */}
{activeStats.includes('plays') &&
(type === 'users' ? (
(isUserDashboard ? (
<>
{data.shows_plays_count > 0 && (
<li className='icon-stat-wrapper'>
Expand Down Expand Up @@ -191,7 +196,7 @@ export default function MediaItem({
)}
{/* Requests */}
{activeStats.includes('requests') &&
type === 'users' &&
isUserDashboard &&
data.requests > 0 && (
<li className='icon-stat-wrapper'>
<QuestionMarkCircleIcon />
Expand Down
15 changes: 12 additions & 3 deletions src/components/MediaItem/MediaItemTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,23 @@ type Props = {
data: TautulliItemRow
type: string
parentRef: RefObject<HTMLDivElement>
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<HTMLSpanElement>(null)
const numberRef = useRef<HTMLSpanElement>(null)
const isLoggedIn = String(data.user_id) === loggedInUserId
const controls = useAnimation()
const isUsersDashboard = type === 'users'

useEffect(() => {
function checkWidth() {
Expand Down Expand Up @@ -76,7 +85,7 @@ export default function MediaItemTitle({ i, data, type, parentRef }: Props) {
}, [controls, parentRef])

return (
<h3 className='mb-2 flex sm:text-xl'>
<h3 className={clsx('mb-2 flex sm:text-xl', isLoggedIn && 'gradient-plex')}>
<span className='mr-1.5 inline-flex items-baseline gap-1' ref={numberRef}>
<span className={clsx('font-bold', topColors[i] || 'text-white')}>
#{i + 1}{' '}
Expand Down Expand Up @@ -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}
</motion.span>
</span>
</h3>
Expand Down
3 changes: 3 additions & 0 deletions src/components/MediaItem/MediaItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Props = {
serverId: string
rows?: boolean
settings: Settings
loggedInUserId?: string
}

export default function MediaItems({
Expand All @@ -17,6 +18,7 @@ export default function MediaItems({
serverId,
rows,
settings,
loggedInUserId,
}: Props) {
return (
<ul
Expand All @@ -37,6 +39,7 @@ export default function MediaItems({
serverId={serverId}
activeStats={settings.dashboard.activeItemStatistics}
settings={settings}
loggedInUserId={loggedInUserId}
/>
))}
</ul>
Expand Down
1 change: 1 addition & 0 deletions src/types/tautulli.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ export type TautulliItemRow = {
movies_plays_count: number
shows_plays_count: number
user_id: number
rank: number
}
2 changes: 1 addition & 1 deletion src/utils/formatting.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export function secondsToTime(seconds: number): string {
if (seconds <= 0) {
return ''
return '0 mins'
}

const units = [
Expand Down

0 comments on commit 4dadd77

Please sign in to comment.