feat: all time rank display (#149)

* add all time rank to item pages

* fix artist albums component

* add no rows check

* fix rewind page
This commit is contained in:
Gabe Farrell 2026-01-16 01:03:23 -05:00 committed by GitHub
parent d08e05220f
commit 5e294b839c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 301 additions and 202 deletions

View file

@ -367,6 +367,7 @@ type Track = {
musicbrainz_id: string;
time_listened: number;
first_listen: number;
all_time_rank: number;
};
type Artist = {
id: number;
@ -378,6 +379,7 @@ type Artist = {
time_listened: number;
first_listen: number;
is_primary: boolean;
all_time_rank: number;
};
type Album = {
id: number;
@ -389,6 +391,7 @@ type Album = {
musicbrainz_id: string;
time_listened: number;
first_listen: number;
all_time_rank: number;
};
type Alias = {
id: number;
@ -459,9 +462,9 @@ type NowPlaying = {
};
type RewindStats = {
title: string;
top_artists: Artist[];
top_albums: Album[];
top_tracks: Track[];
top_artists: Ranked<Artist>[];
top_albums: Ranked<Album>[];
top_tracks: Ranked<Track>[];
minutes_listened: number;
avg_minutes_listened_per_day: number;
plays: number;

View file

@ -8,11 +8,11 @@ interface Props {
period: string;
}
export default function ArtistAlbums({ artistId, name, period }: Props) {
export default function ArtistAlbums({ artistId, name }: Props) {
const { isPending, isError, data, error } = useQuery({
queryKey: [
"top-albums",
{ limit: 99, period: "all_time", artist_id: artistId, page: 0 },
{ limit: 99, period: "all_time", artist_id: artistId },
],
queryFn: ({ queryKey }) => getTopAlbums(queryKey[1] as getItemsArgs),
});
@ -39,16 +39,20 @@ export default function ArtistAlbums({ artistId, name, period }: Props) {
<h3>Albums featuring {name}</h3>
<div className="flex flex-wrap gap-8">
{data.items.map((item) => (
<Link to={`/album/${item.id}`} className="flex gap-2 items-start">
<Link
to={`/album/${item.item.id}`}
className="flex gap-2 items-start"
>
<img
src={imageUrl(item.image, "medium")}
alt={item.title}
src={imageUrl(item.item.image, "medium")}
alt={item.item.title}
style={{ width: 130 }}
/>
<div className="w-[180px] flex flex-col items-start gap-1">
<p>{item.title}</p>
<p>{item.item.title}</p>
<p className="text-sm color-fg-secondary">
{item.listen_count} play{item.listen_count > 1 ? "s" : ""}
{item.item.listen_count} play
{item.item.listen_count > 1 ? "s" : ""}
</p>
</div>
</Link>

View file

@ -8,9 +8,9 @@ interface Props {
}
export default function Rewind(props: Props) {
const artistimg = props.stats.top_artists[0]?.image;
const albumimg = props.stats.top_albums[0]?.image;
const trackimg = props.stats.top_tracks[0]?.image;
const artistimg = props.stats.top_artists[0]?.item.image;
const albumimg = props.stats.top_albums[0]?.item.image;
const trackimg = props.stats.top_tracks[0]?.item.image;
if (
!props.stats.top_artists[0] ||
!props.stats.top_albums[0] ||

View file

@ -1,7 +1,9 @@
import type { Ranked } from "api/api";
type TopItemProps<T> = {
title: string;
imageSrc: string;
items: T[];
items: Ranked<T>[];
getLabel: (item: T) => string;
includeTime?: boolean;
};
@ -28,23 +30,23 @@ export function RewindTopItem<
<div className="flex items-center gap-2">
<div className="flex flex-col items-start mb-2">
<h2>{getLabel(top)}</h2>
<h2>{getLabel(top.item)}</h2>
<span className="text-(--color-fg-tertiary) -mt-3 text-sm">
{`${top.listen_count} plays`}
{`${top.item.listen_count} plays`}
{includeTime
? ` (${Math.floor(top.time_listened / 60)} minutes)`
? ` (${Math.floor(top.item.time_listened / 60)} minutes)`
: ``}
</span>
</div>
</div>
{rest.map((e) => (
<div key={e.id} className="text-sm">
{getLabel(e)}
<div key={e.item.id} className="text-sm">
{getLabel(e.item)}
<span className="text-(--color-fg-tertiary)">
{` - ${e.listen_count} plays`}
{` - ${e.item.listen_count} plays`}
{includeTime
? ` (${Math.floor(e.time_listened / 60)} minutes)`
? ` (${Math.floor(e.item.time_listened / 60)} minutes)`
: ``}
</span>
</div>

View file

@ -30,6 +30,7 @@ export default function Album() {
title={album.title}
img={album.image}
id={album.id}
rank={album.all_time_rank}
musicbrainzId={album.musicbrainz_id}
imgItemId={album.id}
mergeFunc={mergeAlbums}

View file

@ -36,6 +36,7 @@ export default function Artist() {
title={artist.name}
img={artist.image}
id={artist.id}
rank={artist.all_time_rank}
musicbrainzId={artist.musicbrainz_id}
imgItemId={artist.id}
mergeFunc={mergeArtists}

View file

@ -28,6 +28,7 @@ interface Props {
title: string;
img: string;
id: number;
rank: number;
musicbrainzId: string;
imgItemId: number;
mergeFunc: MergeFunc;
@ -96,7 +97,15 @@ export default function MediaLayout(props: Props) {
</div>
<div className="flex flex-col items-start">
<h3>{props.type}</h3>
<h1>{props.title}</h1>
<div className="flex">
<h1>
{props.title}
<span className="text-xl font-medium text-(--color-fg-secondary)">
{" "}
#{props.rank}
</span>
</h1>
</div>
{props.subContent}
</div>
<div className="absolute left-1 sm:right-1 sm:left-auto -top-9 sm:top-1 flex gap-3 items-center">

View file

@ -34,6 +34,7 @@ export default function Track() {
title={track.title}
img={track.image}
id={track.id}
rank={track.all_time_rank}
musicbrainzId={track.musicbrainz_id}
imgItemId={track.album_id}
mergeFunc={mergeTracks}