- <%= if can?(@current_user, :update, Mv.Membership.Group) do %>
+ <%= if can?(@current_user, :update, @group) do %>
<.button variant="primary" navigate={~p"/groups/#{@group.slug}/edit"}>
{gettext("Edit")}
<% end %>
- <%= if can?(@current_user, :destroy, Mv.Membership.Group) do %>
+ <%= if can?(@current_user, :destroy, @group) do %>
<.button class="btn-error" phx-click="open_delete_modal">
{gettext("Delete")}
@@ -132,7 +135,7 @@ defmodule MvWeb.GroupLive.Show do
)}
- <%= if can?(@current_user, :update, Mv.Membership.Group) do %>
+ <%= if can?(@current_user, :update, @group) do %>
<%= if assigns[:show_add_member_input] do %>
@@ -263,7 +266,7 @@ defmodule MvWeb.GroupLive.Show do
{gettext("Name")}
{gettext("Email")}
- <%= if can?(@current_user, :update, Mv.Membership.Group) do %>
+ <%= if can?(@current_user, :update, @group) do %>
{gettext("Actions")}
<% end %>
@@ -291,7 +294,7 @@ defmodule MvWeb.GroupLive.Show do
—
<% end %>
- <%= if can?(@current_user, :update, Mv.Membership.Group) do %>
+ <%= if can?(@current_user, :update, @group) do %>
assign(:show_add_member_input, true)
- |> assign(:member_search_query, "")
- |> assign(:available_members, [])
- |> assign(:selected_member_ids, [])
- |> assign(:selected_members, [])
- |> assign(:show_member_dropdown, false)
- |> assign(:focused_member_index, nil)}
+ # Load candidate members once (single DB read). Search/focus then filter in memory (R2).
+ socket =
+ socket
+ |> assign(:show_add_member_input, true)
+ |> assign(:member_search_query, "")
+ |> assign(:selected_member_ids, [])
+ |> assign(:selected_members, [])
+ |> assign(:show_member_dropdown, false)
+ |> assign(:focused_member_index, nil)
+ |> load_add_member_candidates()
+
+ {:noreply, socket}
end
@impl true
def handle_event("show_member_dropdown", _params, socket) do
- # Use existing group.members for filtering; reload only on add/remove
+ # Filter in memory from preloaded candidates; no DB read (R2).
+ query = socket.assigns.member_search_query || ""
+
socket =
socket
- |> load_available_members("")
+ |> assign(
+ :available_members,
+ filter_candidates_in_memory(socket.assigns.add_member_candidates, query)
+ )
|> assign(:show_member_dropdown, true)
|> assign(:focused_member_index, nil)
@@ -462,6 +472,7 @@ defmodule MvWeb.GroupLive.Show do
|> assign(:show_add_member_input, false)
|> assign(:member_search_query, "")
|> assign(:available_members, [])
+ |> assign(:add_member_candidates, [])
|> assign(:selected_member_ids, [])
|> assign(:selected_members, [])
|> assign(:show_member_dropdown, false)
@@ -528,11 +539,13 @@ defmodule MvWeb.GroupLive.Show do
@impl true
def handle_event("search_members", %{"member_search" => query}, socket) do
- # Use existing group.members for filtering; reload only on add/remove
+ # Filter in memory from preloaded candidates; no DB read (R2).
+ candidates = socket.assigns.add_member_candidates || []
+
socket =
socket
|> assign(:member_search_query, query)
- |> load_available_members(query)
+ |> assign(:available_members, filter_candidates_in_memory(candidates, query))
|> assign(:show_member_dropdown, true)
|> assign(:focused_member_index, nil)
@@ -656,47 +669,69 @@ defmodule MvWeb.GroupLive.Show do
end
end
- defp load_available_members(socket, query) do
+ # Load candidate members once when opening add-member UI (single DB read).
+ defp load_add_member_candidates(socket) do
require Ash.Query
- current_member_ids = group_member_ids_set(socket.assigns.group)
- base_query = available_members_base_query(query)
-
- # Fetch more than 10, then exclude already-in-group and take 10 (avoids empty dropdown when first N are all in group)
- fetch_limit = 50
- limited_query = Ash.Query.limit(base_query, fetch_limit)
+ group = socket.assigns.group
+ exclude_ids = group_member_ids_set(group) |> MapSet.to_list()
actor = current_actor(socket)
- case Ash.read(limited_query, actor: actor, domain: Mv.Membership) do
- {:ok, members} ->
- available =
- members
- |> Enum.reject(fn m -> MapSet.member?(current_member_ids, m.id) end)
- |> Enum.take(10)
+ if exclude_ids == [] do
+ # No members in group; load first N members
+ query =
+ Mv.Membership.Member
+ |> Ash.Query.sort([:last_name, :first_name])
+ |> Ash.Query.limit(300)
- assign(socket, available_members: available)
+ do_load_add_member_candidates(socket, query, actor)
+ else
+ query =
+ Mv.Membership.Member
+ |> Ash.Query.filter(expr(id not in ^exclude_ids))
+ |> Ash.Query.sort([:last_name, :first_name])
+ |> Ash.Query.limit(300)
+
+ do_load_add_member_candidates(socket, query, actor)
+ end
+ end
+
+ defp do_load_add_member_candidates(socket, query, actor) do
+ case Ash.read(query, actor: actor, domain: Mv.Membership) do
+ {:ok, candidates} ->
+ socket
+ |> assign(:add_member_candidates, candidates)
+ |> assign(:available_members, Enum.take(candidates, 10))
{:error, error} ->
- Logger.warning("Failed to load available members for group: #{inspect(error)}")
+ Logger.warning("Failed to load add-member candidates: #{inspect(error)}")
socket
- |> put_flash(:error, gettext("Could not load member search. Please try again."))
+ |> put_flash(:error, gettext("Could not load member list. Please try again."))
+ |> assign(:add_member_candidates, [])
|> assign(:available_members, [])
end
end
- defp available_members_base_query(query) do
- search_query = if query && String.trim(query) != "", do: String.trim(query), else: nil
+ # Filter preloaded candidates by query string (name/email). No DB read. R2.
+ defp filter_candidates_in_memory(candidates, query) when is_list(candidates) do
+ q = if is_binary(query), do: String.trim(query) |> String.downcase(), else: ""
- if search_query do
- Mv.Membership.Member
- |> Ash.Query.for_read(:search, %{query: search_query})
+ if q == "" do
+ candidates |> Enum.take(10)
else
- Mv.Membership.Member
- |> Ash.Query.new()
+ candidates
+ |> Enum.filter(fn m ->
+ name = MemberHelpers.display_name(m) |> String.downcase()
+ email = (m.email || "") |> String.downcase()
+ String.contains?(name, q) or String.contains?(email, q)
+ end)
+ |> Enum.take(10)
end
end
+ defp filter_candidates_in_memory(_, _), do: []
+
defp group_member_ids_set(group) do
members = group.members || []
members |> Enum.map(& &1.id) |> MapSet.new()
@@ -736,6 +771,7 @@ defmodule MvWeb.GroupLive.Show do
|> assign(:show_add_member_input, false)
|> assign(:member_search_query, "")
|> assign(:available_members, [])
+ |> assign(:add_member_candidates, [])
|> assign(:selected_member_ids, [])
|> assign(:selected_members, [])
|> assign(:show_member_dropdown, false)
diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po
index 0d661cf..0caac5c 100644
--- a/priv/gettext/de/LC_MESSAGES/default.po
+++ b/priv/gettext/de/LC_MESSAGES/default.po
@@ -2264,11 +2264,6 @@ msgstr "Nicht berechtigt."
msgid "Could not load data fields. Please check your permissions."
msgstr "Datenfelder konnten nicht geladen werden. Bitte überprüfe deine Berechtigungen."
-#: lib/mv_web/live/group_live/show.ex
-#, elixir-autogen, elixir-format
-msgid "Could not load member search. Please try again."
-msgstr "Mitgliedersuche konnte nicht geladen werden. Bitte versuchen Sie es erneut."
-
#: lib/mv_web/live/group_live/show.ex
#, elixir-autogen, elixir-format
msgid "Add Member"
@@ -2609,17 +2604,7 @@ msgstr "Import"
msgid "Value type cannot be changed after creation"
msgstr "Der Wertetyp kann nach dem Erstellen nicht mehr geändert werden."
-#~ #: lib/mv_web/live/import_export_live.ex
-#~ #, elixir-autogen, elixir-format, fuzzy
-#~ msgid "Export Members (CSV)"
-#~ msgstr "Mitglieder exportieren (CSV)"
-
-#~ #: lib/mv_web/live/import_export_live.ex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "Export functionality will be available in a future release."
-#~ msgstr "Export-Funktionalität ist im nächsten release verfügbar."
-
-#~ #: lib/mv_web/live/import_export_live.ex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "Import members from CSV files or export member data."
-#~ msgstr "Importiere Mitglieder aus CSV-Dateien oder exportiere Mitgliederdaten."
+#: lib/mv_web/live/group_live/show.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Could not load member list. Please try again."
+msgstr "Mitgliederliste konnte nicht geladen werden. Bitte versuche es erneut."
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index 0aef1b3..a72caff 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -2265,11 +2265,6 @@ msgstr ""
msgid "Could not load data fields. Please check your permissions."
msgstr ""
-#: lib/mv_web/live/group_live/show.ex
-#, elixir-autogen, elixir-format
-msgid "Could not load member search. Please try again."
-msgstr ""
-
#: lib/mv_web/live/group_live/show.ex
#, elixir-autogen, elixir-format
msgid "Add Member"
@@ -2609,3 +2604,8 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Value type cannot be changed after creation"
msgstr ""
+
+#: lib/mv_web/live/group_live/show.ex
+#, elixir-autogen, elixir-format
+msgid "Could not load member list. Please try again."
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 371a028..b8c1bf3 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -2265,11 +2265,6 @@ msgstr ""
msgid "Could not load data fields. Please check your permissions."
msgstr ""
-#: lib/mv_web/live/group_live/show.ex
-#, elixir-autogen, elixir-format
-msgid "Could not load member search. Please try again."
-msgstr ""
-
#: lib/mv_web/live/group_live/show.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Add Member"
@@ -2610,17 +2605,7 @@ msgstr ""
msgid "Value type cannot be changed after creation"
msgstr ""
-#~ #: lib/mv_web/live/import_export_live.ex
-#~ #, elixir-autogen, elixir-format, fuzzy
-#~ msgid "Export Members (CSV)"
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/import_export_live.ex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "Export functionality will be available in a future release."
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/import_export_live.ex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "Import members from CSV files or export member data."
-#~ msgstr ""
+#: lib/mv_web/live/group_live/show.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Could not load member list. Please try again."
+msgstr ""