From f1e3b22f9173c3fb2e620a921571cc3e453c3c0f Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Mon, 16 Mar 2026 11:56:39 +0000 Subject: [PATCH] Make online store templates searchable --- lib/music_library/online_store_templates.ex | 44 +++++++ .../live/online_store_template_live/index.ex | 109 ++++++++++++++---- priv/gettext/default.pot | 5 + priv/gettext/en/LC_MESSAGES/default.po | 5 + .../online_store_templates_test.exs | 43 +++++++ .../online_store_template_live/index_test.exs | 20 +++- 6 files changed, 201 insertions(+), 25 deletions(-) diff --git a/lib/music_library/online_store_templates.ex b/lib/music_library/online_store_templates.ex index f0e9fed2..b3448a90 100644 --- a/lib/music_library/online_store_templates.ex +++ b/lib/music_library/online_store_templates.ex @@ -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 diff --git a/lib/music_library_web/live/online_store_template_live/index.ex b/lib/music_library_web/live/online_store_template_live/index.ex index 403904f3..a097bfb8 100644 --- a/lib/music_library_web/live/online_store_template_live/index.ex +++ b/lib/music_library_web/live/online_store_template_live/index.ex @@ -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""" -
-
-
-

- {gettext("Online Store Templates")} -

-
-
- <.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")} - -
+
+
+ <.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")} +
@@ -31,6 +34,16 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do phx-update="stream" id="templates" > + +
  • + + <.pagination id={:bottom_pagination} pagination_params={@list_params} />
    <.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)} /> @@ -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 diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 7fb2ba8a..0f2370ed 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -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 "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 31a2bbfd..d0bf008b 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -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 "" diff --git a/test/music_library/online_store_templates_test.exs b/test/music_library/online_store_templates_test.exs index 99363acc..7a236311 100644 --- a/test/music_library/online_store_templates_test.exs +++ b/test/music_library/online_store_templates_test.exs @@ -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} = diff --git a/test/music_library_web/live/online_store_template_live/index_test.exs b/test/music_library_web/live/online_store_template_live/index_test.exs index 0d389e84..6bb4b81c 100644 --- a/test/music_library_web/live/online_store_template_live/index_test.exs +++ b/test/music_library_web/live/online_store_template_live/index_test.exs @@ -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