Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shared pagination/sorting/search functionality #102

Merged
merged 13 commits into from
Feb 6, 2024
87 changes: 77 additions & 10 deletions lib/beacon/live_admin/components/admin_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Beacon.LiveAdmin.AdminComponents do

alias Phoenix.LiveView.JS
alias Beacon.LiveAdmin.CoreComponents
alias Beacon.LiveAdmin.PageBuilder.Table

import Beacon.LiveAdmin.Gettext
import Beacon.LiveAdmin.Router, only: [beacon_live_admin_path: 3]
Expand Down Expand Up @@ -623,20 +624,86 @@ defmodule Beacon.LiveAdmin.AdminComponents do
end

@doc """
Renders navigation by defined pagination.
Renders a search input text to filter table results.
"""
attr :table, Beacon.LiveAdmin.PageBuilder.Table, required: true
attr :placeholder, :string

def table_search(assigns) do
~H"""
<.simple_form :let={f} for={%{}} as={:search} phx-change="beacon:table-search">
<.input type="search" field={f[:query]} value={@table.query} autofocus={true} placeholder={@placeholder || "Search"} phx-debounce={200} />
</.simple_form>
"""
end

@doc """
Renders a select input to sort table results.
"""
attr :current_page, :integer, required: true
attr :pages, :integer, required: true
attr :table, Beacon.LiveAdmin.PageBuilder.Table, required: true
attr :options, :list, required: true

def pagination(assigns) do
def table_sort(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>
<.simple_form :let={f} for={%{}} as={:sort} phx-change="beacon:table-sort">
<.input type="select" field={f[:sort_by]} value={@table.sort_by} options={@options} />
</.simple_form>
"""
end

@doc """
Renders pagination to nagivate table results.
"""
attr :socket, Phoenix.LiveView.Socket, required: true
attr :page, Beacon.LiveAdmin.PageBuilder.Page, required: true
attr :limit, :integer, default: 11

def table_pagination(assigns) do
assigns = assign(assigns, :table, assigns.page.table)

~H"""
<div :if={@table.page_count > 1} class="flex flex-row justify-center space-x-6 pt-8 text-xl font-semibold pt-10 pb-10">
<.link
:if={@table.current_page > 1}
patch={Table.prev_path(@socket, @page)}
class="border-b-4 border-transparent hover:text-blue-700 hover:border-blue-700 active:text-blue-800 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large"
>
<.icon name="hero-arrow-long-left-solid" class="mr-2" /> prev
</.link>
<span :if={@table.current_page == 1} class="font-heading font-semibold text-gray-400 cursor-default">
<.icon name="hero-arrow-long-left-solid" class="mr-2" /> prev
</span>

<%= for page <- Beacon.LiveAdmin.PageBuilder.Table.nav_pages(@table.current_page, @table.page_count, @limit) do %>
<span :if={is_integer(page)}>
<.link
patch={Table.goto_path(@socket, @page, page)}
class={
if @table.current_page == page,
do:
"px-3 pb-0.5 pt-1.5 border-b-4 border-transparent hover:text-blue-700 hover:border-blue-700 active:text-blue-800 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large text-blue-600 border-blue-600",
else:
"px-3 pb-0.5 pt-1.5 border-b-4 border-transparent hover:text-blue-700 hover:border-blue-700 active:text-blue-800 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large"
}
>
<%= page %>
</.link>
</span>
<span :if={page == :sep}>
...
</span>
<% end %>

<.link
:if={@table.current_page < @table.page_count}
patch={Table.next_path(@socket, @page)}
class="border-b-4 border-transparent hover:text-blue-700 hover:border-blue-700 active:text-blue-800 focus:outline-none focus:duration-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-300 focus-visible:rounded focus-visible:duration-300 transition-link duration-300 only-large"
>
next <.icon name="hero-arrow-long-right-solid" class="mr-2" />
</.link>
<span :if={@table.current_page == @table.page_count} class="font-heading font-semibold text-gray-400 cursor-default">
next <.icon name="hero-arrow-long-right-solid" class="mr-2" />
</span>
</div>
"""
end
Expand Down
4 changes: 2 additions & 2 deletions lib/beacon/live_admin/content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ defmodule Beacon.LiveAdmin.Content do
call(site, Beacon.Content, :list_pages, [site, opts])
end

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

def change_page_variant(site, variant, attrs \\ %{}) do
Expand Down
130 changes: 23 additions & 107 deletions lib/beacon/live_admin/live/page_editor_live/index.ex
Original file line number Diff line number Diff line change
@@ -1,100 +1,39 @@
defmodule Beacon.LiveAdmin.PageEditorLive.Index do
@moduledoc false

use Beacon.LiveAdmin.PageBuilder
use Beacon.LiveAdmin.PageBuilder, table: [sort_by: "title"]

alias Beacon.LiveAdmin.Content

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,
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}")}
{:ok, stream_configure(socket, :pages, dom_id: &"#{Ecto.UUID.generate()}-#{&1.id}")}
end

@impl true
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
)

{:noreply,
socket
|> assign(sort: sort, query: query)
|> stream(:pages, pages, reset: true)}
end
socket =
Table.handle_params(socket, params, &Content.count_pages(&1.site, query: params["query"]))

@impl true
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
%{site: site} = socket.assigns.beacon_page

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
%{per_page: per_page, current_page: page, query: query, sort_by: sort_by} =
socket.assigns.beacon_page.table

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?page=#{page}"
pages =
list_pages(site,
per_page: per_page,
page: page,
query: query,
sort: sort_by
)

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

@impl true
Expand All @@ -109,16 +48,14 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
</:actions>
</.header>

<.simple_form :let={f} for={%{}} as={:search} phx-change="search">
<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 class="flex justify-between">
<div class="basis-10/12">
<.table_search table={@beacon_page.table} placeholder="Search by path or title (showing up to 15 results)" />
</div>
<div class="basis-1/12">
<.table_sort table={@beacon_page.table} options={[{"Title", "title"}, {"Path", "path"}]} />
</div>
</.simple_form>
</div>

<.main_content class="h-[calc(100vh_-_210px)]">
<.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}>
Expand All @@ -135,7 +72,7 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
</:action>
</.table>

<.pagination :if={@pages > 0} current_page={@page} pages={@pages} />
<.table_pagination socket={@socket} page={@beacon_page} />
</.main_content>
"""
end
Expand All @@ -148,28 +85,7 @@ defmodule Beacon.LiveAdmin.PageEditorLive.Index do
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
10 changes: 9 additions & 1 deletion lib/beacon/live_admin/page_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ end

defmodule Beacon.LiveAdmin.PageBuilder.Page do
@moduledoc false
defstruct site: nil, path: nil, module: nil, params: %{}, session: %{}
defstruct site: nil, path: nil, module: nil, params: %{}, session: %{}, table: nil
end

# https://github.com/phoenixframework/phoenix_live_dashboard/blob/32fef8da6a7df97f92f05bd6e7aab33be4036490/lib/phoenix/live_dashboard/page_builder.ex
Expand All @@ -19,6 +19,7 @@ defmodule Beacon.LiveAdmin.PageBuilder do
"""

use Phoenix.Component
alias Beacon.LiveAdmin.PageBuilder.Table
alias Phoenix.LiveView.Socket

@type session :: map()
Expand Down Expand Up @@ -71,6 +72,7 @@ defmodule Beacon.LiveAdmin.PageBuilder do

import Phoenix.LiveView
alias Phoenix.LiveView.JS
alias Beacon.LiveAdmin.PageBuilder.Table

@behaviour Beacon.LiveAdmin.PageBuilder

Expand All @@ -79,6 +81,12 @@ defmodule Beacon.LiveAdmin.PageBuilder do

def init(opts), do: {:ok, opts}
defoverridable init: 1

def __beacon_page_table__ do
unquote(opts)
|> Keyword.get(:table)
|> Table.build()
end
end
end

Expand Down
Loading
Loading