diff --git a/lib/music_library/scrobble_activity.ex b/lib/music_library/scrobble_activity.ex
index 2d3bc348..541d7d08 100644
--- a/lib/music_library/scrobble_activity.ex
+++ b/lib/music_library/scrobble_activity.ex
@@ -240,6 +240,40 @@ defmodule MusicLibrary.ScrobbleActivity do
Repo.all(query)
end
+ @doc """
+ Gets the top artists by scrobble count for the given number of days.
+ Returns a list of maps with artist information and play counts.
+ """
+ def get_top_artists_by_days(days, opts) do
+ limit = Keyword.get(opts, :limit, 10)
+ current_time = Keyword.get_lazy(opts, :current_time, &DateTime.utc_now/0)
+ timezone = Keyword.get(opts, :timezone, &resolve_timezone!/0)
+
+ cutoff_timestamp =
+ current_time
+ |> DateTime.add(-days, :day)
+ |> NaiveDateTime.beginning_of_day()
+ |> DateTime.from_naive!(timezone)
+ |> DateTime.to_unix()
+
+ query =
+ from t in Track,
+ where: t.scrobbled_at_uts >= ^cutoff_timestamp,
+ group_by: [
+ fragment("json_extract(artist, '$.name')"),
+ fragment("json_extract(artist, '$.musicbrainz_id')")
+ ],
+ select: %{
+ artist_name: fragment("json_extract(artist, '$.name')"),
+ artist_musicbrainz_id: fragment("json_extract(artist, '$.musicbrainz_id')"),
+ play_count: count(t.scrobbled_at_uts)
+ },
+ order_by: [desc: count(t.scrobbled_at_uts)],
+ limit: ^limit
+
+ Repo.all(query)
+ end
+
@doc """
Gets top albums for multiple time periods (30, 90, 365 days).
Returns a map with the results for each period, along with collected and
@@ -268,6 +302,22 @@ defmodule MusicLibrary.ScrobbleActivity do
}
end
+ @doc """
+ Gets top artists for multiple time periods (30, 90, 365 days).
+ Returns a map with the results for each period.
+ """
+ def get_top_artists_by_periods(opts) do
+ last_30_days = get_top_artists_by_days(30, opts)
+ last_90_days = get_top_artists_by_days(90, opts)
+ last_365_days = get_top_artists_by_days(365, opts)
+
+ %{
+ last_30_days: last_30_days,
+ last_90_days: last_90_days,
+ last_365_days: last_365_days
+ }
+ end
+
defp resolve_timezone! do
Application.get_env(:music_library, MusicLibraryWeb)
|> Keyword.fetch!(:timezone)
diff --git a/lib/music_library_web/live/stats_live/index.ex b/lib/music_library_web/live/stats_live/index.ex
index ae11c0a7..b07d9f1c 100644
--- a/lib/music_library_web/live/stats_live/index.ex
+++ b/lib/music_library_web/live/stats_live/index.ex
@@ -150,6 +150,54 @@ defmodule MusicLibraryWeb.StatsLive.Index do
"""
end
+ attr :artists, :list, required: true
+ attr :title, :string, required: true
+
+ def top_artists_by_period(assigns) do
+ ~H"""
+
+
+ {@title}
+
+
+
+

~p"/images/cover-not-found.png" <> "';"}
+ />
+
+ <.icon name="hero-user" class="w-6 h-6 text-zinc-400" />
+
+
+ <.link
+ :if={artist.artist_musicbrainz_id != ""}
+ class="text-sm font-medium text-zinc-900 dark:text-zinc-300 hover:text-zinc-700 dark:hover:text-zinc-400 truncate"
+ navigate={~p"/artists/#{artist.artist_musicbrainz_id}"}
+ >
+ {artist.artist_name}
+
+
+ {artist.artist_name}
+
+
+ <.badge>
+ {artist.play_count}
+
+
+
+
+ """
+ end
+
defp refresh_lastfm_feed_button(assigns) do
~H"""