diff --git a/lib/mv_web/live/contribution_period_live/show.ex b/lib/mv_web/live/contribution_period_live/show.ex index 83d9207..f297bf2 100644 --- a/lib/mv_web/live/contribution_period_live/show.ex +++ b/lib/mv_web/live/contribution_period_live/show.ex @@ -36,7 +36,7 @@ defmodule MvWeb.ContributionPeriodLive.Show do <.mockup_warning /> <.header> - {gettext("Contributions for %{name}", name: "#{@member.first_name} #{@member.last_name}")} + {gettext("Contributions for %{name}", name: MvWeb.MemberLive.Index.display_name(@member))} <:subtitle> {gettext("Contribution type")}: {@member.contribution_type} diff --git a/lib/mv_web/live/custom_field_value_live/form.ex b/lib/mv_web/live/custom_field_value_live/form.ex index 9663927..4ed1a23 100644 --- a/lib/mv_web/live/custom_field_value_live/form.ex +++ b/lib/mv_web/live/custom_field_value_live/form.ex @@ -289,6 +289,6 @@ defmodule MvWeb.CustomFieldValueLive.Form do end defp member_options(members) do - Enum.map(members, &{"#{&1.first_name} #{&1.last_name}", &1.id}) + Enum.map(members, &{MvWeb.MemberLive.Index.display_name(&1), &1.id}) end end diff --git a/lib/mv_web/live/member_live/form.ex b/lib/mv_web/live/member_live/form.ex index d5b2c3a..16ad195 100644 --- a/lib/mv_web/live/member_live/form.ex +++ b/lib/mv_web/live/member_live/form.ex @@ -43,7 +43,7 @@ defmodule MvWeb.MemberLive.Form do

<%= if @member do %> - {@member.first_name} {@member.last_name} + {MvWeb.MemberLive.Index.display_name(@member)} <% else %> {gettext("New Member")} <% end %> diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index fff5517..57aa630 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -1165,6 +1165,62 @@ defmodule MvWeb.MemberLive.Index do end end + @doc """ + Returns a display name for a member. + + Combines first_name and last_name if available, otherwise falls back to email. + This ensures that members without names still have a meaningful display name. + + ## Examples + + iex> member = %Member{first_name: "John", last_name: "Doe", email: "john@example.com"} + iex> display_name(member) + "John Doe" + + iex> member = %Member{first_name: nil, last_name: nil, email: "john@example.com"} + iex> display_name(member) + "john@example.com" + + iex> member = %Member{first_name: "John", last_name: nil, email: "john@example.com"} + iex> display_name(member) + "John" + """ + def display_name(member) do + name_parts = + [member.first_name, member.last_name] + |> Enum.reject(&blank?/1) + |> Enum.map(&String.trim/1) + |> Enum.join(" ") + + if name_parts == "" do + member.email + else + name_parts + end + end + + @doc """ + Checks if a value is blank (nil, empty string, or only whitespace). + + ## Examples + + iex> blank?(nil) + true + + iex> blank?("") + true + + iex> blank?(" ") + true + + iex> blank?("John") + false + """ + def blank?(nil), do: true + def blank?(""), do: true + def blank?(value) when is_binary(value), do: String.trim(value) == "" + def blank?(_), do: false + # Public helper function to format dates for use in templates def format_date(date), do: DateFormatter.format_date(date) diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index 1557ed9..430a601 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -129,7 +129,12 @@ """ } > - {member.first_name} + {if MvWeb.MemberLive.Index.blank?(member.first_name) && + MvWeb.MemberLive.Index.blank?(member.last_name) do + MvWeb.MemberLive.Index.display_name(member) + else + member.first_name + end} <:col :let={member} diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index ef2244e..e9236fd 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -35,7 +35,7 @@ defmodule MvWeb.MemberLive.Show do

- {@member.first_name} {@member.last_name} + {MvWeb.MemberLive.Index.display_name(@member)}

<.button variant="primary" navigate={~p"/members/#{@member}/edit?return_to=show"}> diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index 0639e75..85e5bbb 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -131,7 +131,7 @@ defmodule MvWeb.UserLive.Form do

- {@user.member.first_name} {@user.member.last_name} + {MvWeb.MemberLive.Index.display_name(@user.member)}

{@user.member.email}

@@ -210,7 +210,7 @@ defmodule MvWeb.UserLive.Form do ) ]} > -

{member.first_name} {member.last_name}

+

{MvWeb.MemberLive.Index.display_name(member)}

{member.email}

<% end %> @@ -438,7 +438,7 @@ defmodule MvWeb.UserLive.Form do member_name = if selected_member, - do: "#{selected_member.first_name} #{selected_member.last_name}", + do: MvWeb.MemberLive.Index.display_name(selected_member), else: "" # Store the selected member ID and name in socket state and clear unlink flag diff --git a/lib/mv_web/live/user_live/index.html.heex b/lib/mv_web/live/user_live/index.html.heex index 9a98159..c496ea8 100644 --- a/lib/mv_web/live/user_live/index.html.heex +++ b/lib/mv_web/live/user_live/index.html.heex @@ -51,7 +51,7 @@ <:col :let={user} label={gettext("Linked Member")}> <%= if user.member do %> - {user.member.first_name} {user.member.last_name} + {MvWeb.MemberLive.Index.display_name(user.member)} <% else %> {gettext("No member linked")} <% end %> diff --git a/lib/mv_web/live/user_live/show.ex b/lib/mv_web/live/user_live/show.ex index 777def1..f05a763 100644 --- a/lib/mv_web/live/user_live/show.ex +++ b/lib/mv_web/live/user_live/show.ex @@ -57,7 +57,7 @@ defmodule MvWeb.UserLive.Show do class="text-blue-600 underline hover:text-blue-800" > <.icon name="hero-users" class="inline w-4 h-4 mr-1" /> - {@user.member.first_name} {@user.member.last_name} + {MvWeb.MemberLive.Index.display_name(@user.member)} <% else %> {gettext("No member linked")} diff --git a/test/mv_web/member_live/index_display_name_test.exs b/test/mv_web/member_live/index_display_name_test.exs new file mode 100644 index 0000000..86758c9 --- /dev/null +++ b/test/mv_web/member_live/index_display_name_test.exs @@ -0,0 +1,141 @@ +defmodule MvWeb.MemberLive.Index.DisplayNameTest do + @moduledoc """ + Tests for the display_name/1 helper function in MemberLive.Index. + """ + use Mv.DataCase, async: true + + alias Mv.Membership.Member + alias MvWeb.MemberLive.Index + + describe "display_name/1" do + test "returns full name when both first_name and last_name are present" do + member = %Member{ + first_name: "John", + last_name: "Doe", + email: "john@example.com" + } + + assert Index.display_name(member) == "John Doe" + end + + test "returns email when both first_name and last_name are nil" do + member = %Member{ + first_name: nil, + last_name: nil, + email: "john@example.com" + } + + assert Index.display_name(member) == "john@example.com" + end + + test "returns first_name only when last_name is nil" do + member = %Member{ + first_name: "John", + last_name: nil, + email: "john@example.com" + } + + assert Index.display_name(member) == "John" + end + + test "returns last_name only when first_name is nil" do + member = %Member{ + first_name: nil, + last_name: "Doe", + email: "john@example.com" + } + + assert Index.display_name(member) == "Doe" + end + + test "returns email when first_name and last_name are empty strings" do + member = %Member{ + first_name: "", + last_name: "", + email: "john@example.com" + } + + assert Index.display_name(member) == "john@example.com" + end + + test "returns email when first_name and last_name are whitespace only" do + member = %Member{ + first_name: " ", + last_name: " \t ", + email: "john@example.com" + } + + assert Index.display_name(member) == "john@example.com" + end + + test "trims whitespace from name parts" do + member = %Member{ + first_name: " John ", + last_name: " Doe ", + email: "john@example.com" + } + + assert Index.display_name(member) == "John Doe" + end + + test "handles one empty string and one nil" do + member = %Member{ + first_name: "", + last_name: nil, + email: "john@example.com" + } + + assert Index.display_name(member) == "john@example.com" + end + + test "handles one nil and one empty string" do + member = %Member{ + first_name: nil, + last_name: "", + email: "john@example.com" + } + + assert Index.display_name(member) == "john@example.com" + end + + test "handles one whitespace and one nil" do + member = %Member{ + first_name: " ", + last_name: nil, + email: "john@example.com" + } + + assert Index.display_name(member) == "john@example.com" + end + + test "handles one valid name and one whitespace" do + member = %Member{ + first_name: "John", + last_name: " ", + email: "john@example.com" + } + + assert Index.display_name(member) == "John" + end + + test "handles member with only first_name containing whitespace" do + member = %Member{ + first_name: " John ", + last_name: nil, + email: "john@example.com" + } + + assert Index.display_name(member) == "John" + end + + test "handles member with only last_name containing whitespace" do + member = %Member{ + first_name: nil, + last_name: " Doe ", + email: "john@example.com" + } + + assert Index.display_name(member) == "Doe" + end + end +end