add pagination across all admin and shop views
All checks were successful
deploy / deploy (push) Successful in 1m38s

URL-based offset pagination with ?page=N for bookmarkable pages.
Admin views use push_patch, shop collection uses navigate links.
Responsive on mobile with horizontal-scroll tables and stacking
pagination controls. Includes dev seed script for testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-01 09:42:34 +00:00
parent 7f6fd012a5
commit 3480b326a9
21 changed files with 1485 additions and 211 deletions

View File

@@ -6,8 +6,8 @@ defmodule BerrypodWeb.Admin.Newsletter do
@impl true
def mount(_params, _session, socket) do
counts = Newsletter.count_subscribers_by_status()
subscribers = Newsletter.list_subscribers()
campaigns = Newsletter.list_campaigns()
sub_page = Newsletter.list_subscribers_paginated(page: 1)
camp_page = Newsletter.list_campaigns_paginated(page: 1)
{:ok,
socket
@@ -15,38 +15,47 @@ defmodule BerrypodWeb.Admin.Newsletter do
|> assign(:tab, "overview")
|> assign(:newsletter_enabled, Newsletter.newsletter_enabled?())
|> assign(:status_counts, counts)
|> assign(:subscriber_count, length(subscribers))
|> assign(:campaign_count, length(campaigns))
|> assign(:subscriber_pagination, sub_page)
|> assign(:subscriber_count, sub_page.total_count)
|> assign(:campaign_pagination, camp_page)
|> assign(:campaign_count, camp_page.total_count)
|> assign(:status_filter, "all")
|> assign(:search, "")
|> stream(:subscribers, subscribers)
|> stream(:campaigns, campaigns)}
|> stream(:subscribers, sub_page.items)
|> stream(:campaigns, camp_page.items)}
end
@impl true
def handle_params(%{"tab" => tab}, _uri, socket)
when tab in ~w(overview subscribers campaigns) do
def handle_params(params, _uri, socket) do
tab =
if params["tab"] in ~w(overview subscribers campaigns), do: params["tab"], else: "overview"
page_num = Berrypod.Pagination.parse_page(params)
socket = assign(socket, :tab, tab)
socket =
case tab do
"subscribers" ->
subscribers =
Newsletter.list_subscribers(
page =
Newsletter.list_subscribers_paginated(
status: socket.assigns.status_filter,
search: socket.assigns.search
search: socket.assigns.search,
page: page_num
)
socket
|> assign(:subscriber_count, length(subscribers))
|> stream(:subscribers, subscribers, reset: true)
|> assign(:subscriber_pagination, page)
|> assign(:subscriber_count, page.total_count)
|> stream(:subscribers, page.items, reset: true)
"campaigns" ->
campaigns = Newsletter.list_campaigns()
page = Newsletter.list_campaigns_paginated(page: page_num)
socket
|> assign(:campaign_count, length(campaigns))
|> stream(:campaigns, campaigns, reset: true)
|> assign(:campaign_pagination, page)
|> assign(:campaign_count, page.total_count)
|> stream(:campaigns, page.items, reset: true)
_ ->
socket
@@ -55,8 +64,6 @@ defmodule BerrypodWeb.Admin.Newsletter do
{:noreply, socket}
end
def handle_params(_params, _uri, socket), do: {:noreply, socket}
@impl true
def handle_event("toggle_enabled", _params, socket) do
new_value = !socket.assigns.newsletter_enabled
@@ -71,23 +78,17 @@ defmodule BerrypodWeb.Admin.Newsletter do
end
def handle_event("filter_subscribers", %{"status" => status}, socket) do
subscribers = Newsletter.list_subscribers(status: status, search: socket.assigns.search)
{:noreply,
socket
|> assign(:status_filter, status)
|> assign(:subscriber_count, length(subscribers))
|> stream(:subscribers, subscribers, reset: true)}
|> push_patch(to: ~p"/admin/newsletter?tab=subscribers")}
end
def handle_event("search_subscribers", %{"search" => term}, socket) do
subscribers = Newsletter.list_subscribers(status: socket.assigns.status_filter, search: term)
{:noreply,
socket
|> assign(:search, term)
|> assign(:subscriber_count, length(subscribers))
|> stream(:subscribers, subscribers, reset: true)}
|> push_patch(to: ~p"/admin/newsletter?tab=subscribers")}
end
def handle_event("delete_subscriber", %{"id" => id}, socket) do
@@ -152,12 +153,17 @@ defmodule BerrypodWeb.Admin.Newsletter do
status_filter={@status_filter}
status_counts={@status_counts}
subscriber_count={@subscriber_count}
subscriber_pagination={@subscriber_pagination}
search={@search}
/>
</div>
<div :if={@tab == "campaigns"}>
<.campaigns_tab streams={@streams} campaign_count={@campaign_count} />
<.campaigns_tab
streams={@streams}
campaign_count={@campaign_count}
campaign_pagination={@campaign_pagination}
/>
</div>
"""
end
@@ -254,6 +260,7 @@ defmodule BerrypodWeb.Admin.Newsletter do
attr :status_filter, :string, required: true
attr :status_counts, :map, required: true
attr :subscriber_count, :integer, required: true
attr :subscriber_pagination, Berrypod.Pagination, required: true
attr :search, :string, required: true
defp subscribers_tab(assigns) do
@@ -323,6 +330,13 @@ defmodule BerrypodWeb.Admin.Newsletter do
</:action>
</.table>
<.admin_pagination
:if={@subscriber_count > 0}
page={@subscriber_pagination}
patch={~p"/admin/newsletter"}
params={%{"tab" => "subscribers"}}
/>
<div :if={@subscriber_count == 0} class="text-center py-12 text-base-content/60">
<.icon name="hero-envelope" class="size-12 mx-auto mb-4" />
<p class="text-lg font-medium">No subscribers yet</p>
@@ -336,6 +350,7 @@ defmodule BerrypodWeb.Admin.Newsletter do
attr :streams, :any, required: true
attr :campaign_count, :integer, required: true
attr :campaign_pagination, Berrypod.Pagination, required: true
defp campaigns_tab(assigns) do
~H"""
@@ -370,6 +385,13 @@ defmodule BerrypodWeb.Admin.Newsletter do
</:action>
</.table>
<.admin_pagination
:if={@campaign_count > 0}
page={@campaign_pagination}
patch={~p"/admin/newsletter"}
params={%{"tab" => "campaigns"}}
/>
<div :if={@campaign_count == 0} class="text-center py-12 text-base-content/60">
<.icon name="hero-megaphone" class="size-12 mx-auto mb-4" />
<p class="text-lg font-medium">No campaigns yet</p>