'use client' import { useCallback, useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { authFetch, getApiBase } from '../lib/auth' import PageHeading from '../ui/PageHeading' import { type Stats, BreakdownCard, RecentArtwork, StatsNavigation, StreamingCard, ViewingChart, dateLabel, number } from './components' import './stats.css' export default function InsightsPage() { const router = useRouter() const [days, setDays] = useState(30) const [data, setData] = useState(null) const [busy, setBusy] = useState(true) const [error, setError] = useState('') const [revision, setRevision] = useState(0) const load = useCallback(async (signal: AbortSignal) => { setBusy(true) setError('') setData(null) try { const response = await authFetch(`${getApiBase()}/insights?days=${days}`, { signal }) if (response.status === 401) { router.replace('/login?next=%2Finsights'); return } if (response.status === 403) throw new Error('Your account cannot access viewing stats. Please contact an administrator.') if (!response.ok) { const result = await response.json().catch(() => ({})) throw new Error(typeof result.detail === 'string' ? result.detail : 'Your viewing stats are temporarily unavailable. Please try again shortly.') } const result = await response.json() as Stats if (!signal.aborted) setData(result) } catch (err) { if (!signal.aborted) setError(err instanceof Error ? err.message : 'Could not load your stats.') } finally { if (!signal.aborted) setBusy(false) } }, [days, router]) useEffect(() => { const controller = new AbortController() void load(controller.signal) return () => controller.abort() }, [load, revision]) const summary = data?.summary return (
setRevision((value) => value + 1)}>{busy ? 'Loading…' : 'Refresh stats'}} />
Stats period{[7, 30, 90, 365].map((value) => )}

From Jellystat{data?.updated_at && · Updated {new Date(data.updated_at).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}}

{busy &&

Gathering your stats

Fetching your viewing history from Jellystat.

} {error &&

Stats couldn’t load

{error}

} {data?.state === 'not_configured' &&

Your viewing story starts here

{data.is_admin ? 'Connect your Jellystat instance to bring personal viewing stats into Magent.' : 'Viewing stats will appear here once your administrator connects Jellystat.'}

{data.is_admin && Connect Jellystat}
} {data?.state === 'unlinked' &&

Link your viewing account

Your Magent account needs a Jellyfin identity to find your stats. Sign in using Jellyfin, or ask your administrator to sync Jellyfin users.

} {summary && <>
Minutes watched{number(summary.minutes)}{number(summary.minutes / 60)} hours across {number(summary.plays)} plays
Movies played{number(summary.movies)}Different movies you pressed play on
Episodes played{number(summary.episodes)}Different episodes in your history
Requests made{number(data.requests.total)}{number(data.requests.movies)} movies · {number(data.requests.tv)} TV requests
{summary.plays === 0 &&
No viewing history in this period yet. Try a longer period, or come back after your next watch.
}

A little watch history

{summary.current_streak} days
Current streak

Consecutive viewing days through today or yesterday.

{summary.longest_streak} days
Longest run

Your best streak in this period.

{summary.active_days} days
Time for a story

Days with at least a minute watched.

Most watched

By minutes
{data.top_titles?.length ?
    {data.top_titles.map((title, index) =>
  1. {String(index + 1).padStart(2, '0')}
    {title.title}{title.type === 'series' ? 'TV series' : title.type === 'movie' ? 'Movie' : 'Other media'} · {title.plays} plays
    {number(title.minutes)}min
  2. )}
:

Your favourites will find their place here.

}
} {data &&
{summary &&

Recently watched

Latest 20 plays
{data.recent?.length ?
{data.recent.map((play) =>
{play.series || play.title}{play.series ? `${play.episode} · ${play.title}` : play.type === 'movie' ? 'Movie' : 'Media'}{play.client} · {play.method}
{number(play.minutes)} min
)}
:

Plays recorded by Jellystat will appear here.

}
}

Your requests

View all
{data.requests.total}submitted in the past {days} days
{data.requests.pending} Pending{data.requests.approved} Approved{data.requests.declined} Declined
{data.requests.recent.length > 0 ? :

Something on your watchlist? Make a request.

}
} {summary &&

Private to your account · Stats come from Jellystat and may take a minute to refresh. Counts describe plays, including unfinished watches. Movies are identified from Jellystat’s movie libraries; other media still contributes to watch time. Charts and streaks use UTC and the activity dates recorded by Jellystat.

}
) }