diff --git a/lib/music_library/records.ex b/lib/music_library/records.ex index 6ec84510..70c47051 100644 --- a/lib/music_library/records.ex +++ b/lib/music_library/records.ex @@ -4,15 +4,23 @@ defmodule MusicLibrary.Records do alias MusicLibrary.Records.Record - def list_records do + def list_records(opts \\ []) do + limit = Keyword.get(opts, :limit, 20) + offset = Keyword.get(opts, :offset, 0) + q = from r in Record, order_by: r.artists[0]["sort_name"], - limit: 50 + limit: ^limit, + offset: ^offset Repo.all(q) end + def count_records do + Repo.aggregate(Record, :count) + end + def get_record!(id), do: Repo.get!(Record, id) def get_image!(id) do diff --git a/lib/music_library_web/components/pagination.ex b/lib/music_library_web/components/pagination.ex index 8e515b42..d0f4f6fb 100644 --- a/lib/music_library_web/components/pagination.ex +++ b/lib/music_library_web/components/pagination.ex @@ -4,109 +4,215 @@ defmodule MusicLibraryWeb.Pagination do # alias Phoenix.LiveView.JS use Gettext, backend: MusicLibraryWeb.Gettext - attr :entries, :list, required: true + attr :pagination_params, :map, required: true - def card_footer(assigns) do - assigns = assign(assigns, page_size: Enum.count(assigns.entries)) + def pagination(assigns) do + page_links = generate_page_links(assigns.pagination_params) + assigns = assign(assigns, :page_links, page_links) ~H"""
+ <%!-- Only on smallest viewport --%>
Previous Next
- """ end + + attr :page_number, :integer, required: true + attr :page_size, :integer, required: true + + defp next_link(assigns) do + ~H""" + <.link + class="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0" + patch={"?" <> URI.encode_query(page: @page_number, page_size: @page_size)} + > + Next + + + """ + end + + attr :page_number, :integer, required: true + attr :page_size, :integer, required: true + + defp prev_link(assigns) do + ~H""" + <.link + class="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0" + patch={"?" <> URI.encode_query(page: @page_number, page_size: @page_size)} + > + Previous + + + """ + end + + defp separator(assigns) do + ~H""" + + """ + end + + attr :page_number, :integer, required: true + attr :page_size, :integer, required: true + attr :active, :boolean, default: false + + defp numbered_link(assigns) when assigns.active do + ~H""" + + <%= @page_number %> + + """ + end + + defp numbered_link(assigns) do + ~H""" + <.link + class="relative hidden items-center first:rounded-l-md last:rounded-r-md px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 md:inline-flex" + patch={"?" <> URI.encode_query(page: @page_number, page_size: @page_size)} + > + <%= @page_number %> + + """ + end + + def get_pagination_params(params, total_records) do + %{ + total_entries: total_records, + page: parse_int_or_default(params["page"], 1), + page_size: parse_int_or_default(params["page_size"], 20) + } + end + + def page_to_offset(page, per_page) do + (page - 1) * per_page + end + + defp parse_int_or_default(nil, default), do: default + + defp parse_int_or_default(value, _default) when is_binary(value) do + String.to_integer(value) + end + + defp total_pages(total_entries, page_size) do + without_remainder = div(total_entries, page_size) + + if rem(total_entries, page_size) == 0 do + without_remainder + else + without_remainder + 1 + end + end + + @visible_left_pages 3 + @visible_right_pages 3 + @middle "..." + defp generate_page_links(pagination_params) do + %{ + total_entries: total_entries, + page: page, + page_size: page_size + } = pagination_params + + total_pages = total_pages(total_entries, page_size) + all_pages = Enum.to_list(1..total_pages) + + {left_pages, middle_pages, right_pages} = + Enum.reduce(all_pages, {[], [], []}, fn p, {left_p, middle_p, right_p} -> + cond do + p < page - 2 -> + {left_p ++ [p], middle_p, right_p} + + p > page + 2 -> + {left_p, middle_p, right_p ++ [p]} + + true -> + {left_p, middle_p ++ [p], right_p} + end + end) + + prev_page = if page > 1, do: page - 1 + next_page = if page < total_pages, do: page + 1 + + left_separator = if Enum.count(left_pages) > @visible_left_pages, do: @middle + right_separator = if Enum.count(right_pages) > @visible_right_pages, do: @middle + + visible_left_pages = Enum.take(left_pages, @visible_left_pages) + visible_right_pages = Enum.take(right_pages, -@visible_right_pages) + + %{ + total_pages: total_pages, + prev_page: prev_page, + visible_left_pages: visible_left_pages, + left_separator: left_separator, + middle_pages: middle_pages, + right_separator: right_separator, + visible_right_pages: visible_right_pages, + next_page: next_page + } + end end diff --git a/lib/music_library_web/live/record_live/index.ex b/lib/music_library_web/live/record_live/index.ex index 8032d3e0..ab2b8e28 100644 --- a/lib/music_library_web/live/record_live/index.ex +++ b/lib/music_library_web/live/record_live/index.ex @@ -6,8 +6,17 @@ defmodule MusicLibraryWeb.RecordLive.Index do alias MusicLibrary.Records.Record @impl true - def mount(_params, _session, socket) do - {:ok, stream(socket, :records, Records.list_records())} + def mount(params, _session, socket) do + total_records = Records.count_records() + + pagination_params = get_pagination_params(params, total_records) + offset = page_to_offset(pagination_params.page, pagination_params.page_size) + records = Records.list_records(limit: pagination_params.page_size, offset: offset) + + {:ok, + socket + |> assign(:pagination_params, pagination_params) + |> stream(:records, records)} end @impl true @@ -27,10 +36,25 @@ defmodule MusicLibraryWeb.RecordLive.Index do |> assign(:record, %Record{}) end - defp apply_action(socket, :index, _params) do - socket - |> assign(:page_title, "Listing Records") - |> assign(:record, nil) + defp apply_action(socket, :index, params) do + new_socket = + socket + |> assign(:page_title, "Listing Records") + |> assign(:record, nil) + + total_records = Records.count_records() + pagination_params = get_pagination_params(params, total_records) + + if pagination_params != socket.assigns.pagination_params do + offset = page_to_offset(pagination_params.page, pagination_params.page_size) + records = Records.list_records(limit: pagination_params.page_size, offset: offset) + + new_socket + |> assign(:pagination_params, pagination_params) + |> stream(:records, records, reset: true) + else + new_socket + end end @impl true diff --git a/lib/music_library_web/live/record_live/index.html.heex b/lib/music_library_web/live/record_live/index.html.heex index 0088ae88..398a405a 100644 --- a/lib/music_library_web/live/record_live/index.html.heex +++ b/lib/music_library_web/live/record_live/index.html.heex @@ -55,4 +55,4 @@ /> -<.card_footer entries={@streams.records} /> +<.pagination pagination_params={@pagination_params} />