Implement full-text search for members closes #11 #163

Merged
carla merged 6 commits from feature/11-fulltext-search into main 2025-09-29 14:26:38 +02:00
3 changed files with 97 additions and 0 deletions
Showing only changes of commit 78588cbad9 - Show all commits

View file

@ -0,0 +1,62 @@
defmodule MvWeb.Components.SearchBarComponent do
@moduledoc """
Provides the SearchBar Live-Component.
- uses the DaisyUI search input field
- sends search_changed event to parent live view with a query
"""
use MvWeb, :live_component
@impl true
def update(assigns, socket) do
simon marked this conversation as resolved Outdated
mix test
Compiling 5 files (.ex)
    warning: variable "assigns" is unused (if the variable is not meant to be used, prefix it with an underscore)
    │
 11 │   def update(assigns, socket) do
    │              ~~~~~~~
    │
    └─ lib/mv_web/live/components/search_bar_component.ex:11:14: MvWeb.Components.SearchBarComponent.update/2
``` mix test Compiling 5 files (.ex) warning: variable "assigns" is unused (if the variable is not meant to be used, prefix it with an underscore) │ 11 │ def update(assigns, socket) do │ ~~~~~~~ │ └─ lib/mv_web/live/components/search_bar_component.ex:11:14: MvWeb.Components.SearchBarComponent.update/2 ```
socket =
socket
|> assign_new(:query, fn -> "" end)
|> assign_new(:placeholder, fn -> gettext("Search...") end)
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
<form phx-change="search"
phx-target={@myself}class="flex" role="search" aria-label="Search">
<label class="input">
<svg
class="h-[1em] opacity-50"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
aria-hidden="true"
>
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="2.5"
fill="none"
stroke="currentColor"
>
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</g>
</svg>
<input
type="search"
placeholder={@placeholder}
value={@query}
name="query"
phx-debounce="300"
/>
</label>
</form>
"""
end
@impl true
# Function to handle the search
def handle_event("search", %{"query" => q}, socket) do
# Forward a high level message to the parent
send(self(), {:search_changed, q})
{:noreply, assign(socket, :query, q)}
end
end

View file

@ -1,5 +1,7 @@
defmodule MvWeb.MemberLive.Index do
use MvWeb, :live_view
import Ash.Expr
import Ash.Query
import MvWeb.TableComponents
@impl true
@ -10,12 +12,38 @@ defmodule MvWeb.MemberLive.Index do
{:ok,
socket
|> assign(:page_title, gettext("Members"))
|> assign(:query, "")
|> assign(:sort_field, :first_name)
|> assign(:sort_order, :asc)
|> assign(:members, sorted)
|> assign(:selected_members, [])}
end
# -----------------------------------------------------------------
# Receive messages from any toolbar component
# -----------------------------------------------------------------
# Function to handle search
@impl true
def handle_info({:search_changed, q}, socket) do
members =
Mv.Membership.Member
|> Ash.Query.filter(
carla marked this conversation as resolved Outdated

It seems like this returns an empty list when the search string is empty. Maybe we need a special case for that, so an empty search field shows all members instead?

It seems like this returns an empty list when the search string is empty. Maybe we need a special case for that, so an empty search field shows all members instead?
expr(fragment("search_vector @@ plainto_tsquery('simple', ?)", ^q))
)
|> Ash.read!()
carla marked this conversation as resolved Outdated

Let's remove this before merging.

Let's remove this before merging.
IO.inspect(members)
{:noreply,
socket
|> assign(:query, q)
|> assign(:members, members)}
end
# -----------------------------------------------------------------
# Handle Events
# -----------------------------------------------------------------
@impl true
def handle_event("delete", %{"id" => id}, socket) do
member = Ash.get!(Mv.Membership.Member, id)

View file

@ -8,6 +8,13 @@
</:actions>
</.header>
<.live_component
module={MvWeb.Components.SearchBarComponent}
id="search-bar"
query={@query}
placeholder={gettext("Search...")}
/>
<.table
id="members"
rows={@members}