|
|
@ -2,9 +2,9 @@ defmodule MvWeb.Components.SortHeaderComponent do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Sort Header that can be used as column header and sorts a table:
|
Sort Header that can be used as column header and sorts a table:
|
||||||
Props:
|
Props:
|
||||||
- field: atom() # Ash‑Field for sorting
|
- field: atom() # Ash Field for sorting
|
||||||
|
carla marked this conversation as resolved
Outdated
|
|||||||
- label: string() # Column Heading (can be aan heex templyte)
|
- label: string() # Column Heading (can be an heex template)
|
||||||
|
carla marked this conversation as resolved
Outdated
moritz
commented
typo typo `templyte`
|
|||||||
- sort_field: atom() | nil # current sort-field from parent liveview
|
- sort_field: atom() | nil # current sort field from parent liveview
|
||||||
- sort_order: :asc | :desc | nil # current sorting order
|
- sort_order: :asc | :desc | nil # current sorting order
|
||||||
"""
|
"""
|
||||||
use MvWeb, :live_component
|
use MvWeb, :live_component
|
||||||
|
|
@ -19,6 +19,7 @@ defmodule MvWeb.Components.SortHeaderComponent do
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
|
<div class="tooltip" data-tip={aria_sort(@field, @sort_field, @sort_order)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={aria_sort(@field, @sort_field, @sort_order)}
|
aria-label={aria_sort(@field, @sort_field, @sort_order)}
|
||||||
|
|
@ -38,6 +39,7 @@ defmodule MvWeb.Components.SortHeaderComponent do
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -48,7 +50,7 @@ defmodule MvWeb.Components.SortHeaderComponent do
|
||||||
end
|
end
|
||||||
|
|
||||||
|
carla marked this conversation as resolved
Outdated
moritz
commented
ambiguous Unicode character - in comment. ambiguous Unicode character - in comment.
|
|||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
# Hilfsfunktionen für ARIA‑Attribute & Icon‑SVG
|
# Hilfsfunktionen für ARIA Attribute & Icon SVG
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
defp aria_sort(field, sort_field, dir) when field == sort_field do
|
defp aria_sort(field, sort_field, dir) when field == sort_field do
|
||||||
case dir do
|
case dir do
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
import Ash.Query
|
import Ash.Query
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, gettext("Members"))
|
|> assign(:page_title, gettext("Members"))
|
||||||
|
|
@ -14,7 +14,6 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
|> assign(:selected_members, [])
|
|> assign(:selected_members, [])
|
||||||
|
|
||||||
# We call handle params to use the query from the URL
|
# We call handle params to use the query from the URL
|
||||||
{:noreply, socket} = handle_params(params, nil, socket)
|
|
||||||
{:ok, socket}
|
{:ok, socket}
|
||||||
|
moritz marked this conversation as resolved
Outdated
moritz
commented
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html
"The life-cycle is: mount/3 -> handle_params/3 -> render/1"
Therefore handle_params is called twice, in your mount/3 and afterwards by the LiveView. Each handdle_params loads all the members. Is this line necessary at all? If I just remove it it still works for me.
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -70,6 +69,7 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:sort, field_str}, socket) do
|
def handle_info({:sort, field_str}, socket) do
|
||||||
field = String.to_existing_atom(field_str)
|
field = String.to_existing_atom(field_str)
|
||||||
|
old_field = socket.assigns.sort_field
|
||||||
|
|
||||||
{new_order, new_field} =
|
{new_order, new_field} =
|
||||||
if socket.assigns.sort_field == field do
|
if socket.assigns.sort_field == field do
|
||||||
|
|
@ -79,28 +79,38 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
end
|
end
|
||||||
|
|
||||||
active_id = :"sort_#{new_field}"
|
active_id = :"sort_#{new_field}"
|
||||||
|
old_id = :"sort_#{old_field}"
|
||||||
|
|
||||||
# Update the SortHeader to
|
# Update the new SortHeader
|
||||||
|
moritz marked this conversation as resolved
Outdated
moritz
commented
@moritz wrote in #166 (comment):
maybe something like this could work to reset the old header component:
@moritz wrote in https://git.local-it.org/local-it/mitgliederverwaltung/pulls/166#issuecomment-13801:
> If the rows are sorted by a specific column the column header shows a single arrow in the sorting direction. The other rows show a double arrow (up and down). If I sort for another column the double arrow switches into a single arrow, but the previous column header arrow keeps the same. So it's not possible to see which column is the column sorted by. It would be good to reset the sorting arrows of the previous columns. Maybe something more obvious to highlight which is the current sort column would be nice too.
maybe something like this could work to reset the old header component:
```
old_id = :"sort_#{old_field}"
# Update the SortHeader to
send_update(MvWeb.Components.SortHeaderComponent,
id: old_id,
sort_field: new_field,
sort_order: new_order
)
```
|
|||||||
send_update(MvWeb.Components.SortHeaderComponent,
|
send_update(MvWeb.Components.SortHeaderComponent,
|
||||||
id: active_id,
|
id: active_id,
|
||||||
sort_field: new_field,
|
sort_field: new_field,
|
||||||
sort_order: new_order
|
sort_order: new_order
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Reset the current SortHeader
|
||||||
|
send_update(MvWeb.Components.SortHeaderComponent,
|
||||||
|
id: old_id,
|
||||||
|
sort_field: new_field,
|
||||||
|
sort_order: new_order
|
||||||
|
)
|
||||||
|
moritz marked this conversation as resolved
Outdated
moritz
commented
Best practice: instead of hardcoded paths, render path dynamically: Also the Best practice: instead of hardcoded paths, render path dynamically:
```
current_path = socket.view.__live__() |> elem(0)
new_path = current_path <> "?" <> URI.encode_query(query_params)
```
Also the `~p` sigil could be used for creating a path.
carla
commented
I tried that already before and did not find a simple solution for getting the current path. So I would keep that static for now. I tried that already before and did not find a simple solution for getting the current path. So I would keep that static for now.
|
|||||||
|
|
||||||
|
existing_search_query = socket.assigns.query
|
||||||
|
|
||||||
# Build the URL with queries
|
# Build the URL with queries
|
||||||
query_params = %{
|
query_params = %{
|
||||||
|
"query" => existing_search_query,
|
||||||
"sort_field" => Atom.to_string(new_field),
|
"sort_field" => Atom.to_string(new_field),
|
||||||
"sort_order" => Atom.to_string(new_order)
|
"sort_order" => Atom.to_string(new_order)
|
||||||
}
|
}
|
||||||
|
|
||||||
# "/members" is the path you defined in router.ex
|
# Set the new path with params
|
||||||
new_path = "/members?" <> URI.encode_query(query_params)
|
new_path = ~p"/members?#{query_params}"
|
||||||
|
|
||||||
|
moritz marked this conversation as resolved
Outdated
moritz
commented
This function ignores the sorting. This leads to reset the sorting while searching. This function ignores the sorting. This leads to reset the sorting while searching.
To avoid duplicate code I would recommend to integrate the search logic into `load_members`.
|
|||||||
# Push the new URL
|
# Push the new URL
|
||||||
{:noreply,
|
{:noreply,
|
||||||
push_patch(socket,
|
push_patch(socket,
|
||||||
to: new_path,
|
to: new_path,
|
||||||
# replace true
|
|
||||||
replace: true
|
replace: true
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
@ -108,19 +118,27 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
# Function to handle search
|
# Function to handle search
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:search_changed, q}, socket) do
|
def handle_info({:search_changed, q}, socket) do
|
||||||
members =
|
socket = load_members(socket, q)
|
||||||
if String.trim(q) == "" do
|
|
||||||
Ash.read!(Mv.Membership.Member)
|
|
||||||
else
|
|
||||||
Mv.Membership.Member
|
|
||||||
|> filter(expr(fragment("search_vector @@ plainto_tsquery('simple', ?)", ^q)))
|
|
||||||
|> Ash.read!()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
existing_field_query = socket.assigns.sort_field
|
||||||
|
existing_sort_query = socket.assigns.sort_order
|
||||||
|
|
||||||
|
# Build the URL with queries
|
||||||
|
query_params = %{
|
||||||
|
"query" => q,
|
||||||
|
"sort_field" => existing_field_query,
|
||||||
|
"sort_order" => existing_sort_query
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the new path with params
|
||||||
|
new_path = ~p"/members?#{query_params}"
|
||||||
|
|
||||||
|
# Push the new URL
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
push_patch(socket,
|
||||||
|> assign(:query, q)
|
to: new_path,
|
||||||
|> assign(:members, members)}
|
replace: true
|
||||||
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
# -----------------------------------------------------------------
|
# -----------------------------------------------------------------
|
||||||
|
|
@ -130,8 +148,9 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
def handle_params(params, _url, socket) do
|
def handle_params(params, _url, socket) do
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|
|> maybe_update_search(params)
|
||||||
|> maybe_update_sort(params)
|
|> maybe_update_sort(params)
|
||||||
|> load_members()
|
|> load_members(params["query"])
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
@ -140,7 +159,7 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
# FUNCTIONS
|
# FUNCTIONS
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
# Load members eg based on a query for sorting
|
# Load members eg based on a query for sorting
|
||||||
defp load_members(socket) do
|
defp load_members(socket, search_query) do
|
||||||
query =
|
query =
|
||||||
Mv.Membership.Member
|
Mv.Membership.Member
|
||||||
|> Ash.Query.new()
|
|> Ash.Query.new()
|
||||||
|
|
@ -156,7 +175,12 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
:phone_number,
|
:phone_number,
|
||||||
:join_date
|
:join_date
|
||||||
])
|
])
|
||||||
|> maybe_sort(socket.assigns.sort_field, socket.assigns.sort_order)
|
|
||||||
|
# Apply the search filter first
|
||||||
|
query = apply_search_filter(query, search_query)
|
||||||
|
|
||||||
|
# Apply sorting based on current socket state
|
||||||
|
query = maybe_sort(query, socket.assigns.sort_field, socket.assigns.sort_order)
|
||||||
|
|
||||||
members = Ash.read!(query)
|
members = Ash.read!(query)
|
||||||
assign(socket, :members, members)
|
assign(socket, :members, members)
|
||||||
|
|
@ -166,6 +190,16 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
# Helper Functions
|
# Helper Functions
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
# Function to apply search query
|
||||||
|
defp apply_search_filter(query, search_query) do
|
||||||
|
if search_query && String.trim(search_query) != "" do
|
||||||
|
query
|
||||||
|
|> filter(expr(fragment("search_vector @@ plainto_tsquery('simple', ?)", ^search_query)))
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Functions to toggle sorting order
|
# Functions to toggle sorting order
|
||||||
defp toggle_order(:asc), do: :desc
|
defp toggle_order(:asc), do: :desc
|
||||||
defp toggle_order(:desc), do: :asc
|
defp toggle_order(:desc), do: :asc
|
||||||
|
|
@ -193,4 +227,14 @@ defmodule MvWeb.MemberLive.Index do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_update_sort(socket, _), do: socket
|
defp maybe_update_sort(socket, _), do: socket
|
||||||
|
|
||||||
|
# Function to update search parameters
|
||||||
|
defp maybe_update_search(socket, %{"query" => query}) when query != "" do
|
||||||
|
assign(socket, :query, query)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_update_search(socket, _params) do
|
||||||
|
# Keep the previous search query if no new one is provided
|
||||||
|
socket
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
ambiguous Unicode character
-in comment.