add search to container index

This commit is contained in:
2022-12-03 20:07:51 -05:00
parent 11ef53d1bf
commit 95642061db
26 changed files with 423 additions and 383 deletions

View File

@ -17,16 +17,55 @@ defmodule Cannery.Containers do
iex> list_containers(%User{id: 123})
[%Container{}, ...]
iex> list_containers("cool", %User{id: 123})
[%Container{name: "my cool container"}, ...]
"""
@spec list_containers(User.t()) :: [Container.t()]
def list_containers(%User{id: user_id}) do
Repo.all(
from c in Container,
left_join: t in assoc(c, :tags),
left_join: ag in assoc(c, :ammo_groups),
where: c.user_id == ^user_id,
order_by: c.name,
preload: [tags: t, ammo_groups: ag]
@spec list_containers(search :: nil | String.t(), User.t()) :: [Container.t()]
def list_containers(search \\ nil, %User{id: user_id}) do
from(c in Container,
as: :c,
left_join: t in assoc(c, :tags),
as: :t,
left_join: ag in assoc(c, :ammo_groups),
as: :ag,
where: c.user_id == ^user_id,
order_by: c.name,
preload: [tags: t, ammo_groups: ag]
)
|> list_containers_search(search)
|> Repo.all()
end
defp list_containers_search(query, nil), do: query
defp list_containers_search(query, ""), do: query
defp list_containers_search(query, search) do
trimmed_search = String.trim(search)
query
|> where(
[c: c, t: t],
fragment(
"? @@ websearch_to_tsquery('english', ?)",
c.search,
^trimmed_search
) or
fragment(
"? @@ websearch_to_tsquery('english', ?)",
t.search,
^trimmed_search
)
)
|> order_by(
[c: c],
desc:
fragment(
"ts_rank_cd(?, websearch_to_tsquery('english', ?), 4)",
c.search,
^trimmed_search
)
)
end

View File

@ -10,7 +10,13 @@ defmodule CanneryWeb.ContainerLive.Index do
alias Ecto.Changeset
@impl true
def mount(_params, _session, socket), do: {:ok, socket |> assign(view_table: false)}
def mount(%{"search" => search}, _session, socket) do
{:ok, socket |> assign(view_table: true, search: search) |> display_containers()}
end
def mount(_params, _session, socket) do
{:ok, socket |> assign(view_table: true, search: nil) |> display_containers()}
end
@impl true
def handle_params(params, _url, %{assigns: %{live_action: live_action}} = socket) do
@ -21,7 +27,7 @@ defmodule CanneryWeb.ContainerLive.Index do
%{name: container_name} =
container =
Containers.get_container!(id, current_user)
|> Repo.preload([:tags, :ammo_groups], force: true)
|> Repo.preload([:tags, :ammo_groups])
socket
|> assign(page_title: gettext("Edit %{name}", name: container_name), container: container)
@ -42,19 +48,18 @@ defmodule CanneryWeb.ContainerLive.Index do
socket
|> assign(
page_title: gettext("Containers"),
container: nil
container: nil,
search: nil
)
|> display_containers()
end
defp apply_action(socket, :table, _params) do
defp apply_action(socket, :search, %{"search" => search}) do
socket
|> assign(
page_title: gettext("Containers"),
container: nil,
view_table: true
search: search
)
|> display_containers()
end
defp apply_action(%{assigns: %{current_user: current_user}} = socket, :edit_tags, %{"id" => id}) do
@ -104,17 +109,22 @@ defmodule CanneryWeb.ContainerLive.Index do
@impl true
def handle_event("toggle_table", _params, %{assigns: %{view_table: view_table}} = socket) do
new_path =
if view_table,
do: Routes.container_index_path(Endpoint, :index),
else: Routes.container_index_path(Endpoint, :table)
{:noreply, socket |> assign(view_table: !view_table) |> push_patch(to: new_path)}
{:noreply, socket |> assign(:view_table, !view_table) |> display_containers()}
end
defp display_containers(%{assigns: %{current_user: current_user}} = socket) do
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.container_index_path(Endpoint, :index))}
end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply,
socket |> push_patch(to: Routes.container_index_path(Endpoint, :search, search_term))}
end
defp display_containers(%{assigns: %{search: search, current_user: current_user}} = socket) do
containers =
Containers.list_containers(current_user) |> Repo.preload([:tags, :ammo_groups], force: true)
Containers.list_containers(search, current_user) |> Repo.preload([:tags, :ammo_groups])
columns =
[
@ -243,7 +253,4 @@ defmodule CanneryWeb.ContainerLive.Index do
end
defp get_value_for_key(key, container), do: container |> Map.get(key)
def return_to(true = _view_table), do: Routes.container_index_path(Endpoint, :table)
def return_to(false = _view_table), do: Routes.container_index_path(Endpoint, :index)
end

View File

@ -3,7 +3,7 @@
<%= gettext("Containers") %>
</h1>
<%= if @containers |> Enum.empty?() do %>
<%= if @containers |> Enum.empty?() and @search |> is_nil() do %>
<h2 class="title text-xl text-primary-600">
<%= gettext("No containers") %>
<%= display_emoji("😔") %>
@ -17,7 +17,23 @@
<%= dgettext("actions", "New Container") %>
</.link>
<div class="flex flex-col justify-center items-center">
<div class="w-full flex flex-col sm:flex-row justify-center items-center space-y-4 sm:space-y-0 sm:space-x-4 max-w-xl">
<.form
:let={f}
for={:search}
phx-change="search"
phx-submit="search"
class="grow self-stretch flex flex-col items-stretch"
data-qa="container_search"
>
<%= text_input(f, :search_term,
class: "input input-primary",
value: @search,
phx_debounce: 300,
placeholder: gettext("Search containers")
) %>
</.form>
<.toggle_button action="toggle_table" value={@view_table}>
<span class="title text-lg text-primary-600">
<%= gettext("View as table") %>
@ -27,77 +43,86 @@
<% end %>
<div class="w-full flex flex-row flex-wrap justify-center items-center">
<%= if @view_table do %>
<.live_component
module={CanneryWeb.Components.TableComponent}
id="containers_index_table"
action={@live_action}
columns={@columns}
rows={@rows}
/>
<%= if @containers |> Enum.empty?() do %>
<h2 class="title text-xl text-primary-600">
<%= gettext("No containers") %>
<%= display_emoji("😔") %>
</h2>
<% else %>
<%= for container <- @containers do %>
<.container_card container={container}>
<:tag_actions>
<div class="mx-4 my-2">
<.link
patch={Routes.container_index_path(Endpoint, :edit_tags, container)}
class="text-primary-600 link"
>
<i class="fa-fw fa-lg fas fa-tags"></i>
</.link>
</div>
</:tag_actions>
<.link
patch={Routes.container_index_path(Endpoint, :edit, container)}
class="text-primary-600 link"
data-qa={"edit-#{container.id}"}
>
<i class="fa-fw fa-lg fas fa-edit"></i>
</.link>
<%= if @view_table do %>
<.live_component
module={CanneryWeb.Components.TableComponent}
id="containers_index_table"
action={@live_action}
columns={@columns}
rows={@rows}
/>
<% else %>
<%= for container <- @containers do %>
<.container_card container={container}>
<:tag_actions>
<div class="mx-4 my-2">
<.link
patch={Routes.container_index_path(Endpoint, :edit_tags, container)}
class="text-primary-600 link"
>
<i class="fa-fw fa-lg fas fa-tags"></i>
</.link>
</div>
</:tag_actions>
<.link
patch={Routes.container_index_path(Endpoint, :edit, container)}
class="text-primary-600 link"
data-qa={"edit-#{container.id}"}
>
<i class="fa-fw fa-lg fas fa-edit"></i>
</.link>
<.link
patch={Routes.container_index_path(Endpoint, :clone, container)}
class="text-primary-600 link"
data-qa={"clone-#{container.id}"}
>
<i class="fa-fw fa-lg fas fa-copy"></i>
</.link>
<.link
patch={Routes.container_index_path(Endpoint, :clone, container)}
class="text-primary-600 link"
data-qa={"clone-#{container.id}"}
>
<i class="fa-fw fa-lg fas fa-copy"></i>
</.link>
<.link
href="#"
class="text-primary-600 link"
phx-click="delete"
phx-value-id={container.id}
data-confirm={
dgettext("prompts", "Are you sure you want to delete %{name}?", name: container.name)
}
data-qa={"delete-#{container.id}"}
>
<i class="fa-fw fa-lg fas fa-trash"></i>
</.link>
</.container_card>
<.link
href="#"
class="text-primary-600 link"
phx-click="delete"
phx-value-id={container.id}
data-confirm={
dgettext("prompts", "Are you sure you want to delete %{name}?",
name: container.name
)
}
data-qa={"delete-#{container.id}"}
>
<i class="fa-fw fa-lg fas fa-trash"></i>
</.link>
</.container_card>
<% end %>
<% end %>
<% end %>
</div>
</div>
<%= if @live_action in [:new, :edit, :clone] do %>
<.modal return_to={return_to(@view_table)}>
<.modal return_to={Routes.container_index_path(Endpoint, :index)}>
<.live_component
module={CanneryWeb.ContainerLive.FormComponent}
id={@container.id || :new}
title={@page_title}
action={@live_action}
container={@container}
return_to={return_to(@view_table)}
return_to={Routes.container_index_path(Endpoint, :index)}
current_user={@current_user}
/>
</.modal>
<% end %>
<%= if @live_action == :edit_tags do %>
<.modal return_to={return_to(@view_table)}>
<.modal return_to={Routes.container_index_path(Endpoint, :index)}>
<.live_component
module={CanneryWeb.ContainerLive.EditTagsComponent}
id={@container.id}

View File

@ -11,18 +11,14 @@ defmodule CanneryWeb.ContainerLive.Show do
alias Phoenix.LiveView.Socket
@impl true
def mount(_params, _session, %{assigns: %{live_action: live_action}} = socket),
do: {:ok, socket |> assign(show_used: false, view_table: live_action == :table)}
def mount(_params, _session, socket),
do: {:ok, socket |> assign(show_used: false, view_table: true)}
@impl true
def handle_params(
%{"id" => id},
_session,
%{assigns: %{current_user: current_user, live_action: live_action}} = socket
) do
def handle_params(%{"id" => id}, _session, %{assigns: %{current_user: current_user}} = socket) do
socket =
socket
|> assign(view_table: live_action == :table)
|> assign(view_table: true)
|> render_container(id, current_user)
{:noreply, socket}
@ -94,17 +90,8 @@ defmodule CanneryWeb.ContainerLive.Show do
end
@impl true
def handle_event(
"toggle_table",
_params,
%{assigns: %{view_table: view_table, container: container}} = socket
) do
new_path =
if view_table,
do: Routes.container_show_path(Endpoint, :show, container),
else: Routes.container_show_path(Endpoint, :table, container)
{:noreply, socket |> push_patch(to: new_path)}
def handle_event("toggle_table", _params, %{assigns: %{view_table: view_table}} = socket) do
{:noreply, socket |> assign(:view_table, !view_table) |> render_container()}
end
@spec render_container(Socket.t(), Container.id(), User.t()) :: Socket.t()

View File

@ -77,16 +77,15 @@ defmodule CanneryWeb.Router do
live "/type/:id/table", AmmoTypeLive.Show, :table
live "/containers", ContainerLive.Index, :index
live "/containers/table", ContainerLive.Index, :table
live "/containers/new", ContainerLive.Index, :new
live "/containers/:id/edit", ContainerLive.Index, :edit
live "/containers/:id/clone", ContainerLive.Index, :clone
live "/containers/:id/edit_tags", ContainerLive.Index, :edit_tags
live "/containers/edit/:id", ContainerLive.Index, :edit
live "/containers/clone/:id", ContainerLive.Index, :clone
live "/containers/edit_tags/:id", ContainerLive.Index, :edit_tags
live "/containers/search/:search", ContainerLive.Index, :search
live "/containers/:id/show", ContainerLive.Show, :show
live "/containers/:id/show/table", ContainerLive.Show, :table
live "/containers/:id/show/edit", ContainerLive.Show, :edit
live "/containers/:id/show/edit_tags", ContainerLive.Show, :edit_tags
live "/container/:id", ContainerLive.Show, :show
live "/container/edit/:id", ContainerLive.Show, :edit
live "/container/edit_tags/:id", ContainerLive.Show, :edit_tags
live "/ammo", AmmoGroupLive.Index, :index
live "/ammo/new", AmmoGroupLive.Index, :new