Reduce member fields closes #273 #319

Merged
carla merged 13 commits from feature/273_member_fields into main 2026-01-07 11:11:40 +01:00
10 changed files with 212 additions and 10 deletions
Showing only changes of commit b59a4ef61a - Show all commits

View file

@ -36,7 +36,7 @@ defmodule MvWeb.ContributionPeriodLive.Show do
<.mockup_warning /> <.mockup_warning />
<.header> <.header>
{gettext("Contributions for %{name}", name: "#{@member.first_name} #{@member.last_name}")} {gettext("Contributions for %{name}", name: MvWeb.MemberLive.Index.display_name(@member))}
<:subtitle> <:subtitle>
{gettext("Contribution type")}: {gettext("Contribution type")}:
<span class="font-semibold">{@member.contribution_type}</span> <span class="font-semibold">{@member.contribution_type}</span>

View file

@ -289,6 +289,6 @@ defmodule MvWeb.CustomFieldValueLive.Form do
end end
defp member_options(members) do 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
end end

View file

@ -43,7 +43,7 @@ defmodule MvWeb.MemberLive.Form do
<h1 class="text-2xl font-bold text-center flex-1"> <h1 class="text-2xl font-bold text-center flex-1">
<%= if @member do %> <%= if @member do %>
{@member.first_name} {@member.last_name} {MvWeb.MemberLive.Index.display_name(@member)}
<% else %> <% else %>
{gettext("New Member")} {gettext("New Member")}
<% end %> <% end %>

View file

@ -1165,6 +1165,62 @@ defmodule MvWeb.MemberLive.Index do
end end
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 # Public helper function to format dates for use in templates
def format_date(date), do: DateFormatter.format_date(date) def format_date(date), do: DateFormatter.format_date(date)

View file

@ -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> </:col>
<:col <:col
:let={member} :let={member}

View file

@ -35,7 +35,7 @@ defmodule MvWeb.MemberLive.Show do
</.button> </.button>
<h1 class="text-2xl font-bold text-center flex-1"> <h1 class="text-2xl font-bold text-center flex-1">
{@member.first_name} {@member.last_name} {MvWeb.MemberLive.Index.display_name(@member)}
</h1> </h1>
<.button variant="primary" navigate={~p"/members/#{@member}/edit?return_to=show"}> <.button variant="primary" navigate={~p"/members/#{@member}/edit?return_to=show"}>

View file

@ -131,7 +131,7 @@ defmodule MvWeb.UserLive.Form do
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<p class="font-medium text-green-900"> <p class="font-medium text-green-900">
{@user.member.first_name} {@user.member.last_name} {MvWeb.MemberLive.Index.display_name(@user.member)}
</p> </p>
<p class="text-sm text-green-700">{@user.member.email}</p> <p class="text-sm text-green-700">{@user.member.email}</p>
</div> </div>
@ -210,7 +210,7 @@ defmodule MvWeb.UserLive.Form do
) )
]} ]}
> >
<p class="font-medium">{member.first_name} {member.last_name}</p> <p class="font-medium">{MvWeb.MemberLive.Index.display_name(member)}</p>
<p class="text-sm text-base-content/70">{member.email}</p> <p class="text-sm text-base-content/70">{member.email}</p>
</div> </div>
<% end %> <% end %>
@ -438,7 +438,7 @@ defmodule MvWeb.UserLive.Form do
member_name = member_name =
if selected_member, if selected_member,
do: "#{selected_member.first_name} #{selected_member.last_name}", do: MvWeb.MemberLive.Index.display_name(selected_member),
else: "" else: ""
# Store the selected member ID and name in socket state and clear unlink flag # Store the selected member ID and name in socket state and clear unlink flag

View file

@ -51,7 +51,7 @@
</:col> </:col>
<:col :let={user} label={gettext("Linked Member")}> <:col :let={user} label={gettext("Linked Member")}>
<%= if user.member do %> <%= if user.member do %>
{user.member.first_name} {user.member.last_name} {MvWeb.MemberLive.Index.display_name(user.member)}
<% else %> <% else %>
<span class="text-base-content/50">{gettext("No member linked")}</span> <span class="text-base-content/50">{gettext("No member linked")}</span>
<% end %> <% end %>

View file

@ -57,7 +57,7 @@ defmodule MvWeb.UserLive.Show do
class="text-blue-600 underline hover:text-blue-800" class="text-blue-600 underline hover:text-blue-800"
> >
<.icon name="hero-users" class="inline w-4 h-4 mr-1" /> <.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)}
</.link> </.link>
<% else %> <% else %>
<span class="italic text-gray-500">{gettext("No member linked")}</span> <span class="italic text-gray-500">{gettext("No member linked")}</span>

View file

@ -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