Make online store templates searchable
This commit is contained in:
@@ -23,6 +23,37 @@ defmodule MusicLibrary.OnlineStoreTemplates do
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@type list_opts :: [query: String.t(), offset: non_neg_integer(), limit: non_neg_integer()]
|
||||
|
||||
@spec list_templates(list_opts()) :: [OnlineStoreTemplate.t()]
|
||||
def list_templates(opts) do
|
||||
query =
|
||||
OnlineStoreTemplate
|
||||
|> order_by([t], fragment("? COLLATE NOCASE ASC", t.name))
|
||||
|> filter_templates(opts)
|
||||
|
||||
query =
|
||||
case Keyword.get(opts, :offset) do
|
||||
nil -> query
|
||||
offset -> from(t in query, offset: ^offset)
|
||||
end
|
||||
|
||||
query =
|
||||
case Keyword.get(opts, :limit) do
|
||||
nil -> query
|
||||
limit -> from(t in query, limit: ^limit)
|
||||
end
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
@spec count_templates(list_opts()) :: non_neg_integer()
|
||||
def count_templates(opts \\ []) do
|
||||
OnlineStoreTemplate
|
||||
|> filter_templates(opts)
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
@spec get_template!(String.t()) :: OnlineStoreTemplate.t()
|
||||
def get_template!(id), do: Repo.get!(OnlineStoreTemplate, id)
|
||||
|
||||
@@ -62,4 +93,17 @@ defmodule MusicLibrary.OnlineStoreTemplates do
|
||||
|> String.replace("{title}", URI.encode_www_form(record.title))
|
||||
|> String.replace("{format}", URI.encode_www_form(format_string))
|
||||
end
|
||||
|
||||
defp filter_templates(query, opts) do
|
||||
case Keyword.get(opts, :query) do
|
||||
q when q in [nil, ""] ->
|
||||
query
|
||||
|
||||
q ->
|
||||
like = "%#{q}%"
|
||||
|
||||
from t in query,
|
||||
where: like(t.name, ^like) or like(t.description, ^like)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
use MusicLibraryWeb, :live_view
|
||||
|
||||
import MusicLibraryWeb.Components.Pagination
|
||||
import MusicLibraryWeb.LiveHelpers.Params
|
||||
|
||||
alias MusicLibrary.OnlineStoreTemplates
|
||||
alias MusicLibrary.OnlineStoreTemplates.OnlineStoreTemplate
|
||||
|
||||
@default_list_params %{
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
query: ""
|
||||
}
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<Layouts.app flash={@flash} current_section={@current_section} socket={@socket}>
|
||||
<header class="mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-zinc-900 dark:text-zinc-100">
|
||||
{gettext("Online Store Templates")}
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<.button variant="solid" size="sm" patch={~p"/online-store-templates/new"}>
|
||||
<.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" />
|
||||
{gettext("Add")}
|
||||
</.button>
|
||||
</div>
|
||||
<header class="gap-6 mb-2">
|
||||
<div class="flex items-center justify-between gap-6 mb-2 mt-2">
|
||||
<.search_form query={@list_params.query} />
|
||||
<.button variant="solid" size="sm" patch={~p"/online-store-templates/new"}>
|
||||
<.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" />
|
||||
{gettext("Add")}
|
||||
</.button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -31,6 +34,16 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
phx-update="stream"
|
||||
id="templates"
|
||||
>
|
||||
<li
|
||||
id="no-templates"
|
||||
class="hidden only:block p-8 text-center bg-zinc-50 dark:bg-zinc-800 rounded-lg"
|
||||
>
|
||||
<.icon name="hero-building-storefront" class="h-12 w-12 text-zinc-400 mx-auto mb-4" />
|
||||
<p class="text-zinc-600 dark:text-zinc-400">
|
||||
{gettext("No online store templates found")}
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li
|
||||
:for={{id, template} <- @streams.templates}
|
||||
id={id}
|
||||
@@ -98,12 +111,14 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<.pagination id={:bottom_pagination} pagination_params={@list_params} />
|
||||
</div>
|
||||
|
||||
<.structured_modal
|
||||
:if={@live_action in [:new, :edit]}
|
||||
id="template-modal"
|
||||
on_close={JS.patch(~p"/online-store-templates")}
|
||||
on_close={JS.patch(back_path(@list_params))}
|
||||
>
|
||||
<.live_component
|
||||
module={MusicLibraryWeb.OnlineStoreTemplateLive.Form}
|
||||
@@ -111,7 +126,7 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
template={@template}
|
||||
patch={~p"/online-store-templates"}
|
||||
patch={back_path(@list_params)}
|
||||
/>
|
||||
</.structured_modal>
|
||||
</Layouts.app>
|
||||
@@ -120,10 +135,7 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:current_section, :online_store_templates)
|
||||
|> stream(:templates, OnlineStoreTemplates.list_templates())}
|
||||
{:ok, assign(socket, :current_section, :online_store_templates)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -131,22 +143,56 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||
defp apply_action(socket, :edit, %{"id" => id} = params) do
|
||||
socket
|
||||
|> apply_fallback_index(params, :templates, &apply_action/3)
|
||||
|> assign(:page_title, gettext("Edit Online Store Template"))
|
||||
|> assign(:template, OnlineStoreTemplates.get_template!(id))
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
defp apply_action(socket, :new, params) do
|
||||
socket
|
||||
|> apply_fallback_index(params, :templates, &apply_action/3)
|
||||
|> assign(:page_title, gettext("New Online Store Template"))
|
||||
|> assign(:template, %OnlineStoreTemplate{})
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
defp apply_action(socket, :index, params) do
|
||||
query = params["query"]
|
||||
total_templates = OnlineStoreTemplates.count_templates(query: query)
|
||||
|
||||
list_params =
|
||||
@default_list_params
|
||||
|> merge_query(query)
|
||||
|> merge_pagination(params, total_templates)
|
||||
|
||||
load_and_assign_templates(socket, list_params)
|
||||
end
|
||||
|
||||
defp load_and_assign_templates(socket, list_params) do
|
||||
offset = page_to_offset(list_params.page, list_params.page_size)
|
||||
|
||||
templates =
|
||||
OnlineStoreTemplates.list_templates(
|
||||
query: list_params.query,
|
||||
offset: offset,
|
||||
limit: list_params.page_size
|
||||
)
|
||||
|
||||
socket
|
||||
|> assign(:list_params, list_params)
|
||||
|> assign(:page_title, gettext("Online Store Templates"))
|
||||
|> assign(:template, nil)
|
||||
|> stream(:templates, templates, reset: true)
|
||||
end
|
||||
|
||||
defp back_path(list_params) do
|
||||
qs =
|
||||
list_params
|
||||
|> Map.take([:page, :page_size, :query])
|
||||
|> Enum.filter(fn {_, v} -> v not in ["", nil] end)
|
||||
|
||||
~p"/online-store-templates?#{qs}"
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -154,7 +200,10 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
{MusicLibraryWeb.OnlineStoreTemplateLive.Form, {:saved, template}},
|
||||
socket
|
||||
) do
|
||||
{:noreply, stream_insert(socket, :templates, template)}
|
||||
{:noreply,
|
||||
socket
|
||||
|> stream_insert(:templates, template)
|
||||
|> load_and_assign_templates(socket.assigns.list_params)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -162,7 +211,10 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
template = OnlineStoreTemplates.get_template!(id)
|
||||
{:ok, _} = OnlineStoreTemplates.delete_template(template)
|
||||
|
||||
{:noreply, stream_delete(socket, :templates, template)}
|
||||
{:noreply,
|
||||
socket
|
||||
|> stream_delete(:templates, template)
|
||||
|> load_and_assign_templates(socket.assigns.list_params)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -174,4 +226,13 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||
|
||||
{:noreply, stream_insert(socket, :templates, updated_template)}
|
||||
end
|
||||
|
||||
def handle_event("search", %{"query" => query}, socket) do
|
||||
qs =
|
||||
@default_list_params
|
||||
|> Map.put(:query, query)
|
||||
|> Map.take([:query, :page, :page_size])
|
||||
|
||||
{:noreply, push_patch(socket, to: ~p"/online-store-templates?#{qs}")}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2303,3 +2303,8 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New Online Store Template"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No online store templates found"
|
||||
msgstr ""
|
||||
|
||||
@@ -2303,3 +2303,8 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New Online Store Template"
|
||||
msgstr ""
|
||||
|
||||
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "No online store templates found"
|
||||
msgstr ""
|
||||
|
||||
@@ -28,6 +28,49 @@ defmodule MusicLibrary.OnlineStoreTemplatesTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_templates/1" do
|
||||
test "filters by name" do
|
||||
online_store_template(%{name: "Amazon UK"})
|
||||
online_store_template(%{name: "Bandcamp"})
|
||||
|
||||
results = OnlineStoreTemplates.list_templates(query: "Amazon")
|
||||
assert length(results) == 1
|
||||
assert hd(results).name == "Amazon UK"
|
||||
end
|
||||
|
||||
test "filters by description" do
|
||||
online_store_template(%{name: "Store A", description: "vinyl marketplace"})
|
||||
online_store_template(%{name: "Store B", description: "digital downloads"})
|
||||
|
||||
results = OnlineStoreTemplates.list_templates(query: "vinyl")
|
||||
assert length(results) == 1
|
||||
assert hd(results).name == "Store A"
|
||||
end
|
||||
|
||||
test "respects offset and limit" do
|
||||
for i <- 1..3, do: online_store_template(%{name: "Store #{i}"})
|
||||
|
||||
results = OnlineStoreTemplates.list_templates(limit: 1)
|
||||
assert length(results) == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "count_templates/1" do
|
||||
test "counts all templates" do
|
||||
online_store_template(%{name: "Amazon"})
|
||||
online_store_template(%{name: "Bandcamp"})
|
||||
|
||||
assert OnlineStoreTemplates.count_templates() == 2
|
||||
end
|
||||
|
||||
test "counts filtered templates" do
|
||||
online_store_template(%{name: "Amazon UK"})
|
||||
online_store_template(%{name: "Bandcamp"})
|
||||
|
||||
assert OnlineStoreTemplates.count_templates(query: "Amazon") == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "create_template/1" do
|
||||
test "creates with valid attrs" do
|
||||
assert {:ok, template} =
|
||||
|
||||
@@ -11,10 +11,28 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.IndexTest do
|
||||
|
||||
conn
|
||||
|> visit(~p"/online-store-templates")
|
||||
|> assert_has("h1", "Online Store Templates")
|
||||
|> assert_has("p", "Amazon UK")
|
||||
|> assert_has("p", "Bandcamp")
|
||||
end
|
||||
|
||||
test "search filters templates", %{conn: conn} do
|
||||
online_store_template(%{name: "Amazon UK"})
|
||||
online_store_template(%{name: "Bandcamp"})
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/online-store-templates")
|
||||
|
||||
assert has_element?(view, "p", "Amazon UK")
|
||||
assert has_element?(view, "p", "Bandcamp")
|
||||
|
||||
view
|
||||
|> form("form", query: "Amazon")
|
||||
|> render_change()
|
||||
|
||||
assert_patch(view, ~p"/online-store-templates?page=1&page_size=50&query=Amazon")
|
||||
|
||||
assert has_element?(view, "p", "Amazon UK")
|
||||
refute has_element?(view, "p", "Bandcamp")
|
||||
end
|
||||
end
|
||||
|
||||
describe "Create template" do
|
||||
|
||||
Reference in New Issue
Block a user