diff --git a/lib/music_library_web/live/stats_live/top_albums.ex b/lib/music_library_web/live/stats_live/top_albums.ex
index 42551b5b..2ef22bdd 100644
--- a/lib/music_library_web/live/stats_live/top_albums.ex
+++ b/lib/music_library_web/live/stats_live/top_albums.ex
@@ -1,111 +1,27 @@
defmodule MusicLibraryWeb.StatsLive.TopAlbums do
- use MusicLibraryWeb, :live_component
+ use MusicLibraryWeb, :html
alias MusicLibrary.Assets.Transform
alias MusicLibrary.ListeningStats
+ alias MusicLibraryWeb.StatsLive.TopByPeriod
+
+ attr :id, :string, required: true
+ attr :timezone, :string, required: true
+ attr :last_updated_uts, :any
def live(assigns) do
~H"""
- <.live_component module={__MODULE__} {assigns} id={@id} />
- """
- end
-
- @impl true
- def render(assigns) do
- ~H"""
-
-
- {gettext("Top Albums")}
-
- <.tabs class="mt-4">
- <.tabs_list active_tab={name_from_period(@period)} variant="segmented" size="xs">
- <:tab
- class="flex-1"
- name="top_albums_last_7_days"
- phx-click={JS.push("set_period", value: %{period: "last_7_days"})}
- phx-target={@myself}
- >
- {gettext("7d")}
-
- <:tab
- class="flex-1"
- name="top_albums_last_30_days"
- phx-click={JS.push("set_period", value: %{period: "last_30_days"})}
- phx-target={@myself}
- >
- {gettext("30d")}
-
- <:tab
- class="flex-1"
- name="top_albums_last_90_days"
- phx-click={JS.push("set_period", value: %{period: "last_90_days"})}
- phx-target={@myself}
- >
- {gettext("90d")}
-
- <:tab
- class="flex-1"
- name="top_albums_last_365_days"
- phx-click={JS.push("set_period", value: %{period: "last_365_days"})}
- phx-target={@myself}
- >
- {gettext("1y")}
-
- <:tab
- class="flex-1"
- name="top_albums_all_time"
- phx-click={JS.push("set_period", value: %{period: "all_time"})}
- phx-target={@myself}
- >
- {gettext("All time")}
-
-
- <.async_result :let={top_albums} assign={@top_albums}>
- <:loading>
-
- <.loading />
-
-
- <.top_albums_by_period albums={top_albums} />
-
-
-
- """
- end
-
- defp name_from_period(period), do: "top_albums_#{period}"
-
- @impl true
- def mount(socket) do
- {:ok,
- socket
- |> assign(:period, :last_7_days)}
- end
-
- @impl true
- def update(assigns, socket) do
- {:ok,
- socket
- |> assign(assigns)
- |> assign_top_albums()}
- end
-
- @impl true
- def handle_event("set_period", %{"period" => period}, socket) do
- {:noreply,
- socket
- |> assign(:period, String.to_existing_atom(period))
- |> assign_top_albums()}
- end
-
- attr :albums, :list, required: true
-
- defp top_albums_by_period(assigns) do
- ~H"""
-
-
+
+ <:item :let={albums}>
-
-
+
+
"""
end
- defp assign_top_albums(socket) do
- %{timezone: timezone, period: period} = socket.assigns
- current_time = DateTime.utc_now()
-
- assign_async(
- socket,
- :top_albums,
- fn ->
- top_albums =
- ListeningStats.get_top_albums_by_period(
- limit: 10,
- current_time: current_time,
- timezone: timezone,
- period: period
- )
-
- {:ok, %{top_albums: top_albums}}
- end,
- reset: true
- )
- end
-
defp navigate_to_record(album) do
cond do
album.collected_record_id ->
diff --git a/lib/music_library_web/live/stats_live/top_artists.ex b/lib/music_library_web/live/stats_live/top_artists.ex
index 5b4d84c2..c1bfe3e7 100644
--- a/lib/music_library_web/live/stats_live/top_artists.ex
+++ b/lib/music_library_web/live/stats_live/top_artists.ex
@@ -1,112 +1,28 @@
defmodule MusicLibraryWeb.StatsLive.TopArtists do
- use MusicLibraryWeb, :live_component
+ use MusicLibraryWeb, :html
import MusicLibraryWeb.RecordComponents, only: [artist_image: 1]
alias MusicLibrary.ListeningStats
+ alias MusicLibraryWeb.StatsLive.TopByPeriod
+
+ attr :id, :string, required: true
+ attr :timezone, :string, required: true
+ attr :last_updated_uts, :any
def live(assigns) do
~H"""
- <.live_component module={__MODULE__} {assigns} id={@id} />
- """
- end
-
- @impl true
- def render(assigns) do
- ~H"""
-
-
- {gettext("Top Artists")}
-
- <.tabs class="mt-4">
- <.tabs_list active_tab={name_from_period(@period)} variant="segmented" size="xs">
- <:tab
- class="flex-1"
- name="top_artists_last_7_days"
- phx-click={JS.push("set_period", value: %{period: "last_7_days"})}
- phx-target={@myself}
- >
- {gettext("7d")}
-
- <:tab
- class="flex-1"
- name="top_artists_last_30_days"
- phx-click={JS.push("set_period", value: %{period: "last_30_days"})}
- phx-target={@myself}
- >
- {gettext("30d")}
-
- <:tab
- class="flex-1"
- name="top_artists_last_90_days"
- phx-click={JS.push("set_period", value: %{period: "last_90_days"})}
- phx-target={@myself}
- >
- {gettext("90d")}
-
- <:tab
- class="flex-1"
- name="top_artists_last_365_days"
- phx-click={JS.push("set_period", value: %{period: "last_365_days"})}
- phx-target={@myself}
- >
- {gettext("1y")}
-
- <:tab
- class="flex-1"
- name="top_artists_all_time"
- phx-click={JS.push("set_period", value: %{period: "all_time"})}
- phx-target={@myself}
- >
- {gettext("All time")}
-
-
- <.async_result :let={top_artists} assign={@top_artists}>
- <:loading>
-
- <.loading />
-
-
- <.top_artists_by_period artists={top_artists} />
-
-
-
- """
- end
-
- defp name_from_period(period), do: "top_artists_#{period}"
-
- @impl true
- def mount(socket) do
- {:ok,
- socket
- |> assign(:period, :last_7_days)}
- end
-
- @impl true
- def update(assigns, socket) do
- {:ok,
- socket
- |> assign(assigns)
- |> assign_top_artists()}
- end
-
- @impl true
- def handle_event("set_period", %{"period" => period}, socket) do
- {:noreply,
- socket
- |> assign(:period, String.to_existing_atom(period))
- |> assign_top_artists()}
- end
-
- attr :artists, :list, required: true
-
- def top_artists_by_period(assigns) do
- ~H"""
-
-
+
+ <:item :let={artists}>
-
-
+
+
"""
end
-
- defp assign_top_artists(socket) do
- %{timezone: timezone, period: period} = socket.assigns
- current_time = DateTime.utc_now()
-
- assign_async(
- socket,
- :top_artists,
- fn ->
- top_artists =
- ListeningStats.get_top_artists_by_period(
- limit: 10,
- current_time: current_time,
- timezone: timezone,
- period: period
- )
-
- {:ok, %{top_artists: top_artists}}
- end,
- reset: true
- )
- end
end
diff --git a/lib/music_library_web/live/stats_live/top_by_period.ex b/lib/music_library_web/live/stats_live/top_by_period.ex
new file mode 100644
index 00000000..2b6c82de
--- /dev/null
+++ b/lib/music_library_web/live/stats_live/top_by_period.ex
@@ -0,0 +1,130 @@
+defmodule MusicLibraryWeb.StatsLive.TopByPeriod do
+ use MusicLibraryWeb, :live_component
+
+ attr :id, :string, required: true
+ attr :title, :string, required: true
+ attr :key, :atom, required: true
+ attr :fetch_fn, :any, required: true
+ attr :timezone, :string, required: true
+ attr :last_updated_uts, :any
+
+ slot :item, required: true
+
+ def live(assigns) do
+ ~H"""
+ <.live_component module={__MODULE__} {assigns} id={@id} />
+ """
+ end
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+
+ {@title}
+
+ <.tabs class="mt-4">
+ <.tabs_list active_tab={name_from_period(@key, @period)} variant="segmented" size="xs">
+ <:tab
+ class="flex-1"
+ name={"#{@key}_last_7_days"}
+ phx-click={JS.push("set_period", value: %{period: "last_7_days"})}
+ phx-target={@myself}
+ >
+ {gettext("7d")}
+
+ <:tab
+ class="flex-1"
+ name={"#{@key}_last_30_days"}
+ phx-click={JS.push("set_period", value: %{period: "last_30_days"})}
+ phx-target={@myself}
+ >
+ {gettext("30d")}
+
+ <:tab
+ class="flex-1"
+ name={"#{@key}_last_90_days"}
+ phx-click={JS.push("set_period", value: %{period: "last_90_days"})}
+ phx-target={@myself}
+ >
+ {gettext("90d")}
+
+ <:tab
+ class="flex-1"
+ name={"#{@key}_last_365_days"}
+ phx-click={JS.push("set_period", value: %{period: "last_365_days"})}
+ phx-target={@myself}
+ >
+ {gettext("1y")}
+
+ <:tab
+ class="flex-1"
+ name={"#{@key}_all_time"}
+ phx-click={JS.push("set_period", value: %{period: "all_time"})}
+ phx-target={@myself}
+ >
+ {gettext("All time")}
+
+
+ <.async_result :let={items} assign={assigns[@key]}>
+ <:loading>
+
+ <.loading />
+
+
+
+
+ {render_slot(@item, items)}
+
+
+
+
+
+ """
+ end
+
+ defp name_from_period(key, period), do: "#{key}_#{period}"
+
+ @impl true
+ def mount(socket) do
+ {:ok, assign(socket, :period, :last_7_days)}
+ end
+
+ @impl true
+ def update(assigns, socket) do
+ {:ok,
+ socket
+ |> assign(assigns)
+ |> assign_data()}
+ end
+
+ @impl true
+ def handle_event("set_period", %{"period" => period}, socket) do
+ {:noreply,
+ socket
+ |> assign(:period, String.to_existing_atom(period))
+ |> assign_data()}
+ end
+
+ defp assign_data(socket) do
+ %{timezone: timezone, period: period, key: key, fetch_fn: fetch_fn} = socket.assigns
+ current_time = DateTime.utc_now()
+
+ assign_async(
+ socket,
+ key,
+ fn ->
+ items =
+ fetch_fn.(
+ limit: 10,
+ current_time: current_time,
+ timezone: timezone,
+ period: period
+ )
+
+ {:ok, %{key => items}}
+ end,
+ reset: true
+ )
+ end
+end
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index 853e6fd3..65ba6535 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -851,8 +851,7 @@ msgstr ""
msgid "Top Artists"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "All time"
msgstr ""
@@ -1304,26 +1303,22 @@ msgstr ""
msgid "Read"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "1y"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "30d"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "7d"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "90d"
msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 677b9039..3a117ed6 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -851,8 +851,7 @@ msgstr ""
msgid "Top Artists"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "All time"
msgstr ""
@@ -1304,26 +1303,22 @@ msgstr ""
msgid "Read"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "1y"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "30d"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format
msgid "7d"
msgstr ""
-#: lib/music_library_web/live/stats_live/top_albums.ex
-#: lib/music_library_web/live/stats_live/top_artists.ex
+#: lib/music_library_web/live/stats_live/top_by_period.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "90d"
msgstr ""