diff --git a/lib/mv_web/live/membership_fee_type_live/index.ex b/lib/mv_web/live/membership_fee_type_live/index.ex
index 609f91f..37c2746 100644
--- a/lib/mv_web/live/membership_fee_type_live/index.ex
+++ b/lib/mv_web/live/membership_fee_type_live/index.ex
@@ -18,15 +18,20 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
alias Mv.MembershipFees
alias Mv.MembershipFees.MembershipFeeType
+ alias Mv.Membership
alias Mv.Membership.Member
alias MvWeb.Helpers.MembershipFeeHelpers
@impl true
def mount(_params, _session, socket) do
+ fee_types = load_membership_fee_types()
+ member_counts = load_member_counts(fee_types)
+
{:ok,
socket
|> assign(:page_title, gettext("Membership Fee Types"))
- |> assign(:membership_fee_types, load_membership_fee_types())}
+ |> assign(:membership_fee_types, fee_types)
+ |> assign(:member_counts, member_counts)}
end
@impl true
@@ -66,7 +71,7 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
<:col :let={mft} label={gettext("Members")}>
- {get_member_count(mft)}
+ {get_member_count(mft, @member_counts)}
<:action :let={mft}>
@@ -82,18 +87,20 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
data-confirm={gettext("Are you sure?")}
class={[
"btn btn-ghost btn-xs",
- if(get_member_count(mft) > 0,
+ if(get_member_count(mft, @member_counts) > 0,
do: "text-error opacity-50 cursor-not-allowed",
else: "text-error"
)
]}
title={
- if get_member_count(mft) > 0,
+ if get_member_count(mft, @member_counts) > 0,
do:
- gettext("Cannot delete - %{count} member(s) assigned", count: get_member_count(mft)),
+ gettext("Cannot delete - %{count} member(s) assigned",
+ count: get_member_count(mft, @member_counts)
+ ),
else: gettext("Delete")
}
- disabled={get_member_count(mft) > 0}
+ disabled={get_member_count(mft, @member_counts) > 0}
>
<.icon name="hero-trash" class="size-4" />
@@ -112,10 +119,12 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
case Ash.destroy(fee_type, domain: MembershipFees) do
:ok ->
updated_types = Enum.reject(socket.assigns.membership_fee_types, &(&1.id == id))
+ updated_counts = Map.delete(socket.assigns.member_counts, id)
{:noreply,
socket
|> assign(:membership_fee_types, updated_types)
+ |> assign(:member_counts, updated_counts)
|> put_flash(:info, gettext("Membership fee type deleted"))}
{:error, error} ->
@@ -131,12 +140,27 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do
|> Ash.read!(domain: MembershipFees)
end
- defp get_member_count(fee_type) do
- # Count members with this fee type
- case Ash.count(Member |> Ash.Query.filter(membership_fee_type_id == ^fee_type.id)) do
- {:ok, count} -> count
- _ -> 0
- end
+ # Loads all member counts for fee types in a single query to avoid N+1 queries
+ defp load_member_counts(fee_types) do
+ fee_type_ids = Enum.map(fee_types, & &1.id)
+
+ # Load all members with membership_fee_type_id in a single query
+ members =
+ Member
+ |> Ash.Query.filter(membership_fee_type_id in ^fee_type_ids)
+ |> Ash.Query.select([:membership_fee_type_id])
+ |> Ash.read!(domain: Membership)
+
+ # Group by membership_fee_type_id and count
+ members
+ |> Enum.group_by(& &1.membership_fee_type_id)
+ |> Enum.map(fn {fee_type_id, members_list} -> {fee_type_id, length(members_list)} end)
+ |> Map.new()
+ end
+
+ # Gets member count from preloaded assigns map
+ defp get_member_count(fee_type, member_counts) do
+ Map.get(member_counts, fee_type.id, 0)
end
defp format_error(%Ash.Error.Invalid{} = error) do