Skip to content

Commit

Permalink
Pages: Sorting and Pagination (#96)
Browse files Browse the repository at this point in the history
* Convert assigns to streams

* Add default opt value for offset

* Count total pages per page amount

* Add basic pagination component

* Add basic styling

* Add lateral pagination support

* Add active page styling

* Add query param support for page

* Add page query param when searching

* Add sorting support

* Inline tailwind classes by state

* Turn pagination into function component

* Include input select on form

* Add query into url state

* Underscored unused variable

* Add conditional rendering

* Update beacon
  • Loading branch information
ChristianTovar authored Jan 26, 2024
1 parent b901584 commit e27e5b3
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 24 deletions.
19 changes: 19 additions & 0 deletions lib/beacon/live_admin/components/admin_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -621,4 +621,23 @@ defmodule Beacon.LiveAdmin.AdminComponents do
</div>
"""
end

@doc """
Renders navigation by defined pagination.
"""
attr :current_page, :integer, required: true
attr :pages, :integer, required: true

def pagination(assigns) do
~H"""
<div class="flex flex-row justify-center space-x-6 pt-8 text-xl font-semibold">
<button phx-click="prev-page" disabled={@current_page == 1} class="px-2 font-medium disabled:text-gray-400">&#8592; prev</button>
<button :for={page <- 1..@pages} phx-click="set-page" phx-value-page={page} class={if @current_page == page, do: "text-indigo-700", else: ""}>
<%= page %>
</button>
<button phx-click="next-page" disabled={@current_page == @pages} class="px-2 font-medium disabled:text-gray-400">next &#8594;</button>
</div>
"""
end
end
9 changes: 4 additions & 5 deletions lib/beacon/live_admin/content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,13 @@ defmodule Beacon.LiveAdmin.Content do
end

def list_pages(site, opts \\ []) do
opts =
opts
|> Keyword.put_new(:query, nil)
|> Keyword.put_new(:per_page, 20)

call(site, Beacon.Content, :list_pages, [site, opts])
end

def count_pages(site) do
call(site, Beacon.Content, :count_pages, [site])
end

def change_page_variant(site, variant, attrs \\ %{}) do
call(site, Beacon.Content, :change_page_variant, [variant, attrs])
end
Expand Down
126 changes: 108 additions & 18 deletions lib/beacon/live_admin/live/page_editor_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,95 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do

on_mount {Beacon.LiveAdmin.Hooks.Authorized, {:page_editor, :index}}

@per_page 20
@default_sort :title

@impl true
def menu_link(_, :index), do: {:root, "Pages"}

@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, :pages, [])}
{:ok,
socket
|> assign(
page: 1,
pages: number_of_pages(socket.assigns.beacon_page.site),
sort: @default_sort,
query: ""
)
|> stream_configure(:pages, dom_id: &"#{Ecto.UUID.generate()}-#{&1.id}")}
end

@impl true
def handle_params(%{"query" => query}, _uri, socket) do
pages = list_pages(socket.assigns.beacon_page.site, query: query)
{:noreply, assign(socket, :pages, pages)}
end
def handle_params(params, _uri, socket) do
query = params["query"]
offset = set_offset(params["page"])
sort = set_sort(params["sort"], socket)
socket = set_page(offset, params["page"], socket)

pages =
list_pages(socket.assigns.beacon_page.site,
per_page: @per_page,
offset: offset,
query: query,
sort: sort
)

def handle_params(_params, _uri, socket) do
pages = list_pages(socket.assigns.beacon_page.site)
{:noreply, assign(socket, :pages, pages)}
{:noreply,
socket
|> assign(sort: sort, query: query)
|> stream(:pages, pages, reset: true)}
end

@impl true
def handle_event("search", %{"search" => %{"query" => query}}, socket) do
def handle_event("search", %{"search" => %{"query" => query, "sort" => sort}}, socket) do
path =
beacon_live_admin_path(
socket,
socket.assigns.beacon_page.site,
"/pages?page=#{socket.assigns.page}&sort=#{sort}#{query_param(query)}"
)

{:noreply,
socket
|> assign(sort: set_sort(sort, socket))
|> push_patch(to: path)}
end

def handle_event("set-page", %{"page" => page}, socket) do
page
|> String.to_integer()
|> set_page(socket)
end

def handle_event("prev-page", _, %{assigns: %{page: 1}} = socket) do
{:noreply, socket}
end

def handle_event("prev-page", _, socket) do
set_page(socket.assigns.page - 1, socket)
end

def handle_event("next-page", _, %{assigns: %{page: page, pages: page}} = socket) do
{:noreply, socket}
end

def handle_event("next-page", _, socket) do
set_page(socket.assigns.page + 1, socket)
end

defp set_page(page, socket) do
path =
beacon_live_admin_path(
socket,
socket.assigns.beacon_page.site,
"/pages?query=#{query}"
"/pages?page=#{page}"
)

{:noreply, push_patch(socket, to: path)}
{:noreply,
socket
|> assign(page: page)
|> push_patch(to: path)}
end

@impl true
Expand All @@ -50,15 +110,22 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
</.header>
<.simple_form :let={f} for={%{}} as={:search} phx-change="search">
<.input field={f[:query]} type="search" autofocus={true} placeholder="Search by path or title (showing up to 20 results)" />
<div class="flex justify-between">
<div class="basis-10/12">
<.input field={f[:query]} value={@query} type="search" autofocus={true} placeholder="Search by path or title (showing up to 20 results)" />
</div>
<div :if={@pages > 0} class="basis-1/12">
<.input type="select" field={f[:sort]} value={@sort} options={[{"Title", "title"}, {"Path", "path"}]} />
</div>
</div>
</.simple_form>
<.main_content class="h-[calc(100vh_-_210px)]">
<.table id="pages" rows={@pages} row_click={fn page -> JS.navigate(beacon_live_admin_path(@socket, @beacon_page.site, "/pages/#{page.id}")) end}>
<:col :let={page} label="Title"><%= page.title %></:col>
<:col :let={page} label="Path"><%= page.path %></:col>
<:col :let={page} label="Status"><%= display_status(page.status) %></:col>
<:action :let={page}>
<.table id="pages" rows={@streams.pages} row_click={fn {_dom_id, page} -> JS.navigate(beacon_live_admin_path(@socket, @beacon_page.site, "/pages/#{page.id}")) end}>
<:col :let={{_, page}} label="Title"><%= page.title %></:col>
<:col :let={{_, page}} label="Path"><%= page.path %></:col>
<:col :let={{_, page}} label="Status"><%= display_status(page.status) %></:col>
<:action :let={{_, page}}>
<div class="sr-only">
<.link navigate={beacon_live_admin_path(@socket, @beacon_page.site, "/pages/#{page.id}")}>Show</.link>
</div>
Expand All @@ -67,19 +134,42 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
</.link>
</:action>
</.table>
<.pagination :if={@pages > 0} current_page={@page} pages={@pages} />
</.main_content>
"""
end

defp list_pages(site, opts \\ []) do
defp list_pages(site, opts) do
site
|> Content.list_pages(opts)
|> Enum.map(fn page ->
Map.put(page, :status, Content.get_latest_page_event(page.site, page.id).event)
end)
end

defp number_of_pages(site) do
site
|> Content.count_pages()
|> Kernel./(@per_page)
|> ceil()
end

defp set_offset(nil), do: 0
defp set_offset(page) when is_binary(page), do: String.to_integer(page) * @per_page - @per_page
defp set_offset(page), do: page * @per_page - @per_page

defp set_page(0, _page, socket), do: socket
defp set_page(_offset, page, socket), do: assign(socket, page: String.to_integer(page))

defp set_sort(nil, socket), do: socket.assigns.sort
defp set_sort("", socket), do: socket.assigns.sort
defp set_sort(sort, _socket), do: String.to_atom(sort)

defp display_status(:unpublished), do: "Unpublished"
defp display_status(:published), do: "Published"
defp display_status(:created), do: "Draft"

defp query_param(""), do: ""
defp query_param(query), do: "&query=#{query}"
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
%{
"accent": {:hex, :accent, "1.1.1", "20257356446d45078b19b91608f74669b407b39af891ee3db9ee6824d1cae19d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6d5afa50d4886e3370e04fa501468cbaa6c4b5fe926f72ccfa844ad9e259adae"},
"beacon": {:git, "https://github.com/beaconCMS/beacon.git", "4fa1eca70e84ca7157b0e5dcbb4cc60f9f10b25f", []},
"beacon": {:git, "https://github.com/beaconCMS/beacon.git", "3edd59790e8d28b9d1610203f41396d3bf434015", []},
"castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.7", "77de20ac77f0e53f20ca82c563520af0237c301a1ec3ab3bc598e8a96c7ee5d9", [:mix], [{:elixir_make, "~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2768b28bf3c2b4f788c995576b39b8cb5d47eb788526d93bd52206c1d8bf4b75"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
Expand Down

0 comments on commit e27e5b3

Please sign in to comment.