diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex
index c8ba7e4..1557ed9 100644
--- a/lib/mv_web/live/member_live/index.html.heex
+++ b/lib/mv_web/live/member_live/index.html.heex
@@ -239,24 +239,6 @@
>
{member.city}
- <:col
- :let={member}
- :if={:phone_number in @member_fields_visible}
- label={
- ~H"""
- <.live_component
- module={MvWeb.Components.SortHeaderComponent}
- id={:sort_phone_number}
- field={:phone_number}
- label={gettext("Phone Number")}
- sort_field={@sort_field}
- sort_order={@sort_order}
- />
- """
- }
- >
- {member.phone_number}
-
<:col
:let={member}
:if={:join_date in @member_fields_visible}
diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex
index c2af0a9..997cb1a 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.Helpers.MemberHelpers.display_name(@member)}
<.button variant="primary" navigate={~p"/members/#{@member}/edit?return_to=show"}>
@@ -104,11 +104,6 @@ defmodule MvWeb.MemberLive.Show do
- <.data_field label={gettext("Phone")} value={@member.phone_number} />
-
<.data_field
diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex
index 0639e75..f0cc1ce 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.Helpers.MemberHelpers.display_name(@user.member)}
{@user.member.email}
@@ -210,7 +210,7 @@ defmodule MvWeb.UserLive.Form do
)
]}
>
-
{member.first_name} {member.last_name}
+
{MvWeb.Helpers.MemberHelpers.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.Helpers.MemberHelpers.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..e7fd72e 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.Helpers.MemberHelpers.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..9eaa4fa 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.Helpers.MemberHelpers.display_name(@user.member)}
<% else %>
{gettext("No member linked")}
diff --git a/lib/mv_web/translations/member_fields.ex b/lib/mv_web/translations/member_fields.ex
index f10e0d2..2d6834a 100644
--- a/lib/mv_web/translations/member_fields.ex
+++ b/lib/mv_web/translations/member_fields.ex
@@ -20,7 +20,6 @@ defmodule MvWeb.Translations.MemberFields do
def label(:first_name), do: gettext("First Name")
def label(:last_name), do: gettext("Last Name")
def label(:email), do: gettext("Email")
- def label(:phone_number), do: gettext("Phone")
def label(:join_date), do: gettext("Join Date")
def label(:exit_date), do: gettext("Exit Date")
def label(:notes), do: gettext("Notes")
diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po
index f55805d..2947204 100644
--- a/priv/gettext/de/LC_MESSAGES/default.po
+++ b/priv/gettext/de/LC_MESSAGES/default.po
@@ -151,11 +151,6 @@ msgstr "Notizen"
msgid "Paid"
msgstr "Bezahlt"
-#: lib/mv_web/live/member_live/index.html.heex
-#, elixir-autogen, elixir-format
-msgid "Phone Number"
-msgstr "Telefonnummer"
-
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/translations/member_fields.ex
@@ -865,13 +860,6 @@ msgstr "Zahlungen"
msgid "Personal Data"
msgstr "Persönliche Daten"
-#: lib/mv_web/live/member_live/form.ex
-#: lib/mv_web/live/member_live/show.ex
-#: lib/mv_web/translations/member_fields.ex
-#, elixir-autogen, elixir-format
-msgid "Phone"
-msgstr "Telefon"
-
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
@@ -2007,6 +1995,18 @@ msgstr "Nicht gesetzt"
#~ msgid "Pending"
#~ msgstr "Ausstehend"
+#~ #: lib/mv_web/live/member_live/form.ex
+#~ #: lib/mv_web/live/member_live/show.ex
+#~ #: lib/mv_web/translations/member_fields.ex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Phone"
+#~ msgstr "Telefon"
+
+#~ #: lib/mv_web/live/member_live/index.html.heex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Phone Number"
+#~ msgstr "Telefonnummer"
+
#~ #: lib/mv_web/live/contribution_settings_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Quarterly Interval - Joining Period Excluded"
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index 4374f23..e6c520e 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -152,11 +152,6 @@ msgstr ""
msgid "Paid"
msgstr ""
-#: lib/mv_web/live/member_live/index.html.heex
-#, elixir-autogen, elixir-format
-msgid "Phone Number"
-msgstr ""
-
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/translations/member_fields.ex
@@ -863,13 +858,6 @@ msgstr ""
msgid "Personal Data"
msgstr ""
-#: lib/mv_web/live/member_live/form.ex
-#: lib/mv_web/live/member_live/show.ex
-#: lib/mv_web/translations/member_fields.ex
-#, elixir-autogen, elixir-format
-msgid "Phone"
-msgstr ""
-
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 0bc0bed..26a5599 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -152,11 +152,6 @@ msgstr ""
msgid "Paid"
msgstr ""
-#: lib/mv_web/live/member_live/index.html.heex
-#, elixir-autogen, elixir-format
-msgid "Phone Number"
-msgstr ""
-
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/index.html.heex
#: lib/mv_web/translations/member_fields.ex
@@ -864,13 +859,6 @@ msgstr ""
msgid "Personal Data"
msgstr ""
-#: lib/mv_web/live/member_live/form.ex
-#: lib/mv_web/live/member_live/show.ex
-#: lib/mv_web/translations/member_fields.ex
-#, elixir-autogen, elixir-format, fuzzy
-msgid "Phone"
-msgstr ""
-
#: lib/mv_web/live/member_live/form.ex
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format, fuzzy
@@ -1405,20 +1393,437 @@ msgstr ""
msgid "String"
msgstr ""
-#~ #: lib/mv_web/live/custom_field_live/show.ex
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format
+msgid "About Membership Fee Types"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Already paid cycles will remain with the old amount."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format
+msgid "An error occurred"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Are you sure you want to delete this cycle?"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Cannot delete - %{count} member(s) assigned"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Change Amount?"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Changing the amount will affect %{count} member(s)."
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Confirm Change"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Current Cycle"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Current amount"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Cycle"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Cycle amount updated"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Cycle deleted"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Cycle status updated"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Cycles regenerated successfully"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Delete Cycle"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Edit Cycle Amount"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Edit Membership Fee Type"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Failed to update cycle status: %{errors}"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Future unpaid cycles will be regenerated with the new amount."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Generate cycles from the last existing cycle to today"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Interval cannot be changed after creation."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Invalid amount format"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format
+msgid "Last Cycle"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format
+msgid "Manage membership fee types for membership fees."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Mark as paid"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Mark as suspended"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Mark as unpaid"
+msgstr ""
+
+#: lib/mv_web/live/member_live/form.ex
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership Fee"
+msgstr ""
+
+#: lib/mv_web/live/member_live/index.html.heex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership Fee Status"
+msgstr ""
+
+#: lib/mv_web/live/member_live/form.ex
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership Fee Type"
+msgstr ""
+
+#: lib/mv_web/components/layouts/navbar.ex
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership Fee Types"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership Fees"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership fee type deleted"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership fee type removed"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Membership fee type saved successfully"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Membership fee type updated. Cycles regenerated."
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Membership fee types define different membership fee structures. Each type has a fixed interval (monthly, quarterly, half-yearly, yearly) that cannot be changed after creation."
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "New Membership Fee Type"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "New amount"
+msgstr ""
+
+#: lib/mv_web/live/member_live/index.html.heex
+#, elixir-autogen, elixir-format
+msgid "No cycle"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format
+msgid "No cycles"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "No membership fee cycles found. Cycles will be generated automatically when a membership fee type is assigned."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "No membership fee type assigned"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format
+msgid "No status"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Please confirm the amount change first"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Regenerate Cycles"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Regenerating..."
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Save Membership Fee Type"
+msgstr ""
+
+#: lib/mv_web/live/member_live/form.ex
+#, elixir-autogen, elixir-format
+msgid "Select a membership fee type for this member. Members can only switch between types with the same interval."
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Select interval"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format
+msgid "Type"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/form.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Use this form to manage membership fee types in your database."
+msgstr ""
+
+#: lib/mv_web/live/member_live/form.ex
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Warning: Changing from %{old_interval} to %{new_interval} is not allowed. Please select a membership fee type with the same interval."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "A cycle for this period already exists"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "All cycles deleted"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Click to edit amount"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Create"
+msgstr "created"
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Create Cycle"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Create a new cycle manually"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Cycle Period"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Cycle created successfully"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Delete All"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Delete All Cycles"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Delete all cycles"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Delete cycle"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Invalid date format"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Payment Interval"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "The cycle period will be calculated based on this date and the interval."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "This action cannot be undone."
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Type '%{confirmation}' to confirm"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Warning"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "You are about to delete all %{count} cycles for this member."
+msgstr ""
+
+#: lib/mv_web/live/member_live/index.html.heex
+#, elixir-autogen, elixir-format
+msgid "Current Cycle Payment Status"
+msgstr "Current Cycle Payment Status"
+
+#: lib/mv_web/live/member_live/index.html.heex
+#, elixir-autogen, elixir-format
+msgid "Last Cycle Payment Status"
+msgstr "Last Cycle Payment Status"
+
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format
+msgid "Delete membership fee type"
+msgstr ""
+
+#: lib/mv_web/live/membership_fee_type_live/index.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Edit membership fee type"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format
+msgid "Confirmation text does not match"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show/membership_fees_component.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "No cycles to delete"
+msgstr ""
+
+#: lib/mv_web/live/member_live/show.ex
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Not set"
+msgstr ""
+
+#~ #: lib/mv_web/live/member_live/index.html.heex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Auto-generated identifier (immutable)"
+#~ msgid "Show current cycle"
#~ msgstr ""
-#~ #: lib/mv_web/live/contribution_settings_live.ex
+#~ #: lib/mv_web/live/member_live/index.html.heex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Configure global settings for membership contributions."
+#~ msgid "Unpaid in last cycle"
#~ msgstr ""
-#~ #: lib/mv_web/live/member_live/form.ex
-#~ #: lib/mv_web/live/member_live/show.ex
+#~ #: lib/mv_web/live/custom_field_live/index_component.ex
+#~ #, elixir-autogen, elixir-format, fuzzy
+#~ msgid "New Custom field"
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/member_live/index.html.heex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Contribution"
+#~ msgid "Show Last/Current Cycle Payment Status"
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/components/payment_filter_component.ex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "All payment statuses"
#~ msgstr ""
#~ #: lib/mv_web/live/member_field_live/index_component.ex
@@ -1426,10 +1831,26 @@ msgstr ""
#~ msgid "Field Name"
#~ msgstr ""
-#~ #: lib/mv_web/components/layouts/navbar.ex
-#~ #: lib/mv_web/live/contribution_settings_live.ex
+#~ #: lib/mv_web/live/member_live/index.html.heex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Contribution Settings"
+#~ msgid "Copy emails"
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/member_live/form.ex
+#~ #: lib/mv_web/live/member_live/show.ex
+#~ #: lib/mv_web/translations/member_fields.ex
+#~ #, elixir-autogen, elixir-format, fuzzy
+#~ msgid "Phone"
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/member_live/show.ex
+#~ #, elixir-autogen, elixir-format, fuzzy
+#~ msgid "Pending"
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/member_live/show.ex
+#~ #, elixir-autogen, elixir-format, fuzzy
+#~ msgid "Payment Cycle"
#~ msgstr ""
#~ #: lib/mv_web/live/member_field_live/index_component.ex
@@ -1444,17 +1865,13 @@ msgstr ""
#~ #: lib/mv_web/live/contribution_settings_live.ex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Contribution start"
+#~ msgid "View Example Member"
#~ msgstr ""
-#~ #: lib/mv_web/live/member_live/index.html.heex
+#~ #: lib/mv_web/live/member_live/form.ex
+#~ #: lib/mv_web/live/member_live/show.ex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Copy emails"
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/contribution_settings_live.ex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "Default Contribution Type"
+#~ msgid "This data is for demonstration purposes only (mockup)."
#~ msgstr ""
#~ #: lib/mv_web/live/contribution_settings_live.ex
@@ -1463,6 +1880,11 @@ msgstr ""
#~ msgid "Edit amount"
#~ msgstr ""
+#~ #: lib/mv_web/live/contribution_settings_live.ex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Quarterly Interval - Joining Period Excluded"
+#~ msgstr ""
+
#~ #: lib/mv_web/live/contribution_settings_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Example: Member Contribution View"
@@ -1473,20 +1895,20 @@ msgstr ""
#~ msgid "Failed to delete some cycles: %{errors}"
#~ msgstr ""
+#~ #: lib/mv_web/live/member_live/index.html.heex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Switch to current cycle"
+#~ msgstr ""
+
#~ #: lib/mv_web/live/membership_fee_settings_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Failed to save settings. Please check the errors below."
#~ msgstr ""
-#~ #: lib/mv_web/live/user_live/index.html.heex
-#~ #: lib/mv_web/live/user_live/show.ex
+#~ #: lib/mv_web/components/layouts/navbar.ex
+#~ #: lib/mv_web/live/contribution_settings_live.ex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Generated periods"
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/custom_field_live/form_component.ex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "Immutable"
+#~ msgid "Contribution Settings"
#~ msgstr ""
#~ #: lib/mv_web/live/contribution_settings_live.ex
@@ -1509,27 +1931,19 @@ msgstr ""
#~ msgid "Not paid"
#~ msgstr ""
+#~ #: lib/mv_web/live/member_live/form.ex
#~ #: lib/mv_web/live/member_live/show.ex
-#~ #, elixir-autogen, elixir-format, fuzzy
-#~ msgid "Payment Cycle"
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/member_live/show.ex
-#~ #, elixir-autogen, elixir-format, fuzzy
-#~ msgid "Pending"
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/contribution_settings_live.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Quarterly Interval - Joining Period Excluded"
#~ msgstr ""
-#~ #: lib/mv_web/live/member_live/index.html.heex
+#~ #: lib/mv_web/live/member_live/form.ex
+#~ #: lib/mv_web/live/member_live/show.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Show Last/Current Cycle Payment Status"
#~ msgstr ""
-#~ #: lib/mv_web/live/member_live/index.html.heex
+#~ #: lib/mv_web/live/components/payment_filter_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Show current cycle"
#~ msgstr ""
@@ -1552,36 +1966,46 @@ msgstr ""
#~ #: lib/mv_web/live/member_live/form.ex
#~ #: lib/mv_web/live/member_live/show.ex
#~ #, elixir-autogen, elixir-format
-#~ msgid "This data is for demonstration purposes only (mockup)."
+#~ msgid "Contribution"
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/user_live/index.html.heex
+#~ #: lib/mv_web/live/user_live/show.ex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Generated periods"
#~ msgstr ""
#~ #: lib/mv_web/live/member_live/index.html.heex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Unpaid in current cycle"
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/member_live/index.html.heex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "Unpaid in last cycle"
+#~ msgid "Switch to last completed cycle"
#~ msgstr ""
#~ #: lib/mv_web/live/contribution_settings_live.ex
#~ #, elixir-autogen, elixir-format
-#~ msgid "View Example Member"
+#~ msgid "Configure global settings for membership contributions."
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/custom_field_live/show.ex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Auto-generated identifier (immutable)"
#~ msgstr ""
#~ #: lib/mv_web/live/contribution_settings_live.ex
#~ #, elixir-autogen, elixir-format
-#~ msgid "Yearly Interval - Joining Period Included"
-#~ msgstr ""
-
-#~ #: lib/mv_web/live/member_live/form.ex
-#~ #: lib/mv_web/live/member_live/show.ex
-#~ #, elixir-autogen, elixir-format
-#~ msgid "monthly"
+#~ msgid "Default Contribution Type"
#~ msgstr ""
#~ #: lib/mv_web/live/member_live/form.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "yearly"
#~ msgstr ""
+
+#~ #: lib/mv_web/live/member_live/index.html.heex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Phone Number"
+#~ msgstr ""
+
+#~ #: lib/mv_web/live/member_live/index.html.heex
+#~ #, elixir-autogen, elixir-format
+#~ msgid "Unpaid in current cycle"
+#~ msgstr ""
diff --git a/priv/repo/migrations/20260102155350_remove_phone_number_and_make_fields_optional.exs b/priv/repo/migrations/20260102155350_remove_phone_number_and_make_fields_optional.exs
new file mode 100644
index 0000000..5943b78
--- /dev/null
+++ b/priv/repo/migrations/20260102155350_remove_phone_number_and_make_fields_optional.exs
@@ -0,0 +1,404 @@
+defmodule Mv.Repo.Migrations.RemovePhoneNumberAndMakeFieldsOptional do
+ @moduledoc """
+ Removes phone_number field from members table and makes first_name/last_name optional.
+
+ This migration:
+ 1. Removes phone_number column from members table
+ 2. Makes first_name and last_name columns nullable
+ 3. Updates members_search_vector_trigger() function to remove phone_number
+ 4. Updates update_member_search_vector_from_custom_field_value() function to remove phone_number
+ 5. Updates existing search_vector values for all members
+ """
+
+ use Ecto.Migration
+
+ def up do
+ # Update the main trigger function to remove phone_number
+ execute("""
+ CREATE OR REPLACE FUNCTION members_search_vector_trigger() RETURNS trigger AS $$
+ DECLARE
+ custom_values_text text;
+ BEGIN
+ -- Aggregate all custom field values for this member
+ -- Support both formats: _union_type/_union_value (Ash format) and type/value (legacy)
+ -- ->> operator always returns TEXT directly (no need for -> + ::text fallback)
+ SELECT string_agg(
+ CASE
+ WHEN value ? '_union_value' THEN value->>'_union_value'
+ WHEN value ? 'value' THEN value->>'value'
+ ELSE ''
+ END,
+ ' '
+ )
+ INTO custom_values_text
+ FROM custom_field_values
+ WHERE member_id = NEW.id AND value IS NOT NULL;
+
+ -- Build search_vector with member fields and custom field values
+ NEW.search_vector :=
+ setweight(to_tsvector('simple', coalesce(NEW.first_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(NEW.last_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(NEW.email, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(NEW.join_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(NEW.exit_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(NEW.notes, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(NEW.city, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(NEW.street, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(NEW.house_number::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(NEW.postal_code::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(custom_values_text, '')), 'C');
+ RETURN NEW;
+ END
+ $$ LANGUAGE plpgsql;
+ """)
+
+ # Update trigger function to remove phone_number
+ execute("""
+ CREATE OR REPLACE FUNCTION update_member_search_vector_from_custom_field_value() RETURNS trigger AS $$
+ DECLARE
+ member_id_val uuid;
+ member_first_name text;
+ member_last_name text;
+ member_email text;
+ member_join_date date;
+ member_exit_date date;
+ member_notes text;
+ member_city text;
+ member_street text;
+ member_house_number text;
+ member_postal_code text;
+ custom_values_text text;
+ old_value_text text;
+ new_value_text text;
+ BEGIN
+ -- Get member ID from trigger context
+ member_id_val := COALESCE(NEW.member_id, OLD.member_id);
+
+ -- Optimization: For UPDATE operations, check if value actually changed
+ -- If value hasn't changed, we can skip the expensive re-aggregation
+ IF TG_OP = 'UPDATE' THEN
+ -- Extract OLD value for comparison (handle both JSONB formats)
+ -- ->> operator always returns TEXT directly
+ old_value_text := COALESCE(
+ NULLIF(OLD.value->>'_union_value', ''),
+ NULLIF(OLD.value->>'value', ''),
+ ''
+ );
+
+ -- Extract NEW value for comparison (handle both JSONB formats)
+ new_value_text := COALESCE(
+ NULLIF(NEW.value->>'_union_value', ''),
+ NULLIF(NEW.value->>'value', ''),
+ ''
+ );
+
+ -- Check if value, member_id, or custom_field_id actually changed
+ -- If nothing changed, skip expensive re-aggregation
+ IF (old_value_text IS NOT DISTINCT FROM new_value_text) AND
+ (OLD.member_id IS NOT DISTINCT FROM NEW.member_id) AND
+ (OLD.custom_field_id IS NOT DISTINCT FROM NEW.custom_field_id) THEN
+ RETURN COALESCE(NEW, OLD);
+ END IF;
+ END IF;
+
+ -- Fetch only required fields instead of full record (performance optimization)
+ SELECT
+ first_name,
+ last_name,
+ email,
+ join_date,
+ exit_date,
+ notes,
+ city,
+ street,
+ house_number,
+ postal_code
+ INTO
+ member_first_name,
+ member_last_name,
+ member_email,
+ member_join_date,
+ member_exit_date,
+ member_notes,
+ member_city,
+ member_street,
+ member_house_number,
+ member_postal_code
+ FROM members
+ WHERE id = member_id_val;
+
+ -- Aggregate all custom field values for this member
+ -- Support both formats: _union_type/_union_value (Ash format) and type/value (legacy)
+ -- ->> operator always returns TEXT directly
+ SELECT string_agg(
+ CASE
+ WHEN value ? '_union_value' THEN value->>'_union_value'
+ WHEN value ? 'value' THEN value->>'value'
+ ELSE ''
+ END,
+ ' '
+ )
+ INTO custom_values_text
+ FROM custom_field_values
+ WHERE member_id = member_id_val AND value IS NOT NULL;
+
+ -- Update the search_vector for the affected member
+ UPDATE members
+ SET search_vector =
+ setweight(to_tsvector('simple', coalesce(member_first_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(member_last_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(member_email, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(member_join_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(member_exit_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(member_notes, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(member_city, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(member_street, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(member_house_number::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(member_postal_code::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(custom_values_text, '')), 'C')
+ WHERE id = member_id_val;
+
+ RETURN COALESCE(NEW, OLD);
+ END
+ $$ LANGUAGE plpgsql;
+ """)
+
+ # Update existing search_vector values for all members
+ execute("""
+ UPDATE members m
+ SET search_vector =
+ setweight(to_tsvector('simple', coalesce(m.first_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(m.last_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(m.email, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(m.join_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(m.exit_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(m.notes, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(m.city, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(m.street, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(m.house_number::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(m.postal_code::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(
+ (SELECT string_agg(
+ CASE
+ WHEN value ? '_union_value' THEN value->>'_union_value'
+ WHEN value ? 'value' THEN value->>'value'
+ ELSE ''
+ END,
+ ' '
+ )
+ FROM custom_field_values
+ WHERE member_id = m.id AND value IS NOT NULL),
+ ''
+ )), 'C')
+ """)
+
+ # Make first_name and last_name nullable
+ execute("ALTER TABLE members ALTER COLUMN first_name DROP NOT NULL")
+ execute("ALTER TABLE members ALTER COLUMN last_name DROP NOT NULL")
+
+ # Remove phone_number column
+ alter table(:members) do
+ remove :phone_number
+ end
+ end
+
+ def down do
+ # Set default values for NULL fields before restoring NOT NULL constraint
+ # This prevents the migration from failing if NULL values exist
+ execute("UPDATE members SET first_name = '' WHERE first_name IS NULL")
+ execute("UPDATE members SET last_name = '' WHERE last_name IS NULL")
+
+ # Restore first_name and last_name as NOT NULL
+ execute("ALTER TABLE members ALTER COLUMN first_name SET NOT NULL")
+ execute("ALTER TABLE members ALTER COLUMN last_name SET NOT NULL")
+
+ # Add phone_number column back
+ alter table(:members) do
+ add :phone_number, :text
+ end
+
+ # Restore trigger functions with phone_number
+ execute("""
+ CREATE OR REPLACE FUNCTION members_search_vector_trigger() RETURNS trigger AS $$
+ DECLARE
+ custom_values_text text;
+ BEGIN
+ -- Aggregate all custom field values for this member
+ -- Support both formats: _union_type/_union_value (Ash format) and type/value (legacy)
+ -- ->> operator always returns TEXT directly (no need for -> + ::text fallback)
+ SELECT string_agg(
+ CASE
+ WHEN value ? '_union_value' THEN value->>'_union_value'
+ WHEN value ? 'value' THEN value->>'value'
+ ELSE ''
+ END,
+ ' '
+ )
+ INTO custom_values_text
+ FROM custom_field_values
+ WHERE member_id = NEW.id AND value IS NOT NULL;
+
+ -- Build search_vector with member fields and custom field values
+ NEW.search_vector :=
+ setweight(to_tsvector('simple', coalesce(NEW.first_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(NEW.last_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(NEW.email, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(NEW.phone_number, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(NEW.join_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(NEW.exit_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(NEW.notes, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(NEW.city, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(NEW.street, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(NEW.house_number::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(NEW.postal_code::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(custom_values_text, '')), 'C');
+ RETURN NEW;
+ END
+ $$ LANGUAGE plpgsql;
+ """)
+
+ execute("""
+ CREATE OR REPLACE FUNCTION update_member_search_vector_from_custom_field_value() RETURNS trigger AS $$
+ DECLARE
+ member_id_val uuid;
+ member_first_name text;
+ member_last_name text;
+ member_email text;
+ member_phone_number text;
+ member_join_date date;
+ member_exit_date date;
+ member_notes text;
+ member_city text;
+ member_street text;
+ member_house_number text;
+ member_postal_code text;
+ custom_values_text text;
+ old_value_text text;
+ new_value_text text;
+ BEGIN
+ -- Get member ID from trigger context
+ member_id_val := COALESCE(NEW.member_id, OLD.member_id);
+
+ -- Optimization: For UPDATE operations, check if value actually changed
+ -- If value hasn't changed, we can skip the expensive re-aggregation
+ IF TG_OP = 'UPDATE' THEN
+ -- Extract OLD value for comparison (handle both JSONB formats)
+ -- ->> operator always returns TEXT directly
+ old_value_text := COALESCE(
+ NULLIF(OLD.value->>'_union_value', ''),
+ NULLIF(OLD.value->>'value', ''),
+ ''
+ );
+
+ -- Extract NEW value for comparison (handle both JSONB formats)
+ new_value_text := COALESCE(
+ NULLIF(NEW.value->>'_union_value', ''),
+ NULLIF(NEW.value->>'value', ''),
+ ''
+ );
+
+ -- Check if value, member_id, or custom_field_id actually changed
+ -- If nothing changed, skip expensive re-aggregation
+ IF (old_value_text IS NOT DISTINCT FROM new_value_text) AND
+ (OLD.member_id IS NOT DISTINCT FROM NEW.member_id) AND
+ (OLD.custom_field_id IS NOT DISTINCT FROM NEW.custom_field_id) THEN
+ RETURN COALESCE(NEW, OLD);
+ END IF;
+ END IF;
+
+ -- Fetch only required fields instead of full record (performance optimization)
+ SELECT
+ first_name,
+ last_name,
+ email,
+ phone_number,
+ join_date,
+ exit_date,
+ notes,
+ city,
+ street,
+ house_number,
+ postal_code
+ INTO
+ member_first_name,
+ member_last_name,
+ member_email,
+ member_phone_number,
+ member_join_date,
+ member_exit_date,
+ member_notes,
+ member_city,
+ member_street,
+ member_house_number,
+ member_postal_code
+ FROM members
+ WHERE id = member_id_val;
+
+ -- Aggregate all custom field values for this member
+ -- Support both formats: _union_type/_union_value (Ash format) and type/value (legacy)
+ -- ->> operator always returns TEXT directly
+ SELECT string_agg(
+ CASE
+ WHEN value ? '_union_value' THEN value->>'_union_value'
+ WHEN value ? 'value' THEN value->>'value'
+ ELSE ''
+ END,
+ ' '
+ )
+ INTO custom_values_text
+ FROM custom_field_values
+ WHERE member_id = member_id_val AND value IS NOT NULL;
+
+ -- Update the search_vector for the affected member
+ UPDATE members
+ SET search_vector =
+ setweight(to_tsvector('simple', coalesce(member_first_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(member_last_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(member_email, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(member_phone_number, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(member_join_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(member_exit_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(member_notes, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(member_city, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(member_street, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(member_house_number::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(member_postal_code::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(custom_values_text, '')), 'C')
+ WHERE id = member_id_val;
+
+ RETURN COALESCE(NEW, OLD);
+ END
+ $$ LANGUAGE plpgsql;
+ """)
+
+ # Update existing search_vector values to include phone_number
+ execute("""
+ UPDATE members m
+ SET search_vector =
+ setweight(to_tsvector('simple', coalesce(m.first_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(m.last_name, '')), 'A') ||
+ setweight(to_tsvector('simple', coalesce(m.email, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(m.phone_number, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(m.join_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(m.exit_date::text, '')), 'D') ||
+ setweight(to_tsvector('simple', coalesce(m.notes, '')), 'B') ||
+ setweight(to_tsvector('simple', coalesce(m.city, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(m.street, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(m.house_number::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(m.postal_code::text, '')), 'C') ||
+ setweight(to_tsvector('simple', coalesce(
+ (SELECT string_agg(
+ CASE
+ WHEN value ? '_union_value' THEN value->>'_union_value'
+ WHEN value ? 'value' THEN value->>'value'
+ ELSE ''
+ END,
+ ' '
+ )
+ FROM custom_field_values
+ WHERE member_id = m.id AND value IS NOT NULL),
+ ''
+ )), 'C')
+ """)
+ end
+end
diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs
index fb102f4..4f99e5b 100644
--- a/priv/repo/seeds.exs
+++ b/priv/repo/seeds.exs
@@ -147,7 +147,6 @@ member_attrs_list = [
last_name: "Müller",
email: "hans.mueller@example.de",
join_date: ~D[2023-01-15],
- phone_number: "+49301234567",
city: "München",
street: "Hauptstraße",
house_number: "42",
@@ -160,7 +159,6 @@ member_attrs_list = [
last_name: "Schmidt",
email: "greta.schmidt@example.de",
join_date: ~D[2023-02-01],
- phone_number: "+49309876543",
city: "Hamburg",
street: "Lindenstraße",
house_number: "17",
@@ -174,7 +172,6 @@ member_attrs_list = [
last_name: "Wagner",
email: "friedrich.wagner@example.de",
join_date: ~D[2022-11-10],
- phone_number: "+49301122334",
city: "Berlin",
street: "Kastanienallee",
house_number: "8",
@@ -186,7 +183,6 @@ member_attrs_list = [
last_name: "Wagner",
email: "marianne.wagner@example.de",
join_date: ~D[2022-11-10],
- phone_number: "+49301122334",
city: "Berlin",
street: "Kastanienallee",
house_number: "8"
@@ -299,7 +295,6 @@ linked_members = [
last_name: "Weber",
email: "maria.weber@example.de",
join_date: ~D[2023-03-15],
- phone_number: "+49301357924",
city: "Frankfurt",
street: "Goetheplatz",
house_number: "5",
@@ -313,7 +308,6 @@ linked_members = [
last_name: "Klein",
email: "thomas.klein@example.de",
join_date: ~D[2023-04-01],
- phone_number: "+49302468135",
city: "Köln",
street: "Rheinstraße",
house_number: "23",
diff --git a/test/membership/member_test.exs b/test/membership/member_test.exs
index 1c4beb1..258d8be 100644
--- a/test/membership/member_test.exs
+++ b/test/membership/member_test.exs
@@ -7,7 +7,6 @@ defmodule Mv.Membership.MemberTest do
first_name: "John",
last_name: "Doe",
email: "john@example.com",
- phone_number: "+49123456789",
join_date: ~D[2020-01-01],
exit_date: nil,
notes: "Test note",
@@ -17,16 +16,14 @@ defmodule Mv.Membership.MemberTest do
postal_code: "12345"
}
- test "First name is required and must not be empty" do
- attrs = Map.put(@valid_attrs, :first_name, "")
- assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
- assert error_message(errors, :first_name) =~ "must be present"
+ test "First name is optional" do
+ attrs = Map.delete(@valid_attrs, :first_name)
+ assert {:ok, _member} = Membership.create_member(attrs)
end
- test "Last name is required and must not be empty" do
- attrs = Map.put(@valid_attrs, :last_name, "")
- assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
- assert error_message(errors, :last_name) =~ "must be present"
+ test "Last name is optional" do
+ attrs = Map.delete(@valid_attrs, :last_name)
+ assert {:ok, _member} = Membership.create_member(attrs)
end
test "Email is required" do
@@ -41,14 +38,6 @@ defmodule Mv.Membership.MemberTest do
assert error_message(errors, :email) =~ "is not a valid email"
end
- test "Phone number is optional but must have a valid format if specified" do
- attrs = Map.put(@valid_attrs, :phone_number, "abc")
- assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
- assert error_message(errors, :phone_number) =~ "is not a valid phone number"
- attrs2 = Map.delete(@valid_attrs, :phone_number)
- assert {:ok, _member} = Membership.create_member(attrs2)
- end
-
test "Join date cannot be in the future" do
attrs = Map.put(@valid_attrs, :join_date, Date.utc_today() |> Date.add(1))
diff --git a/test/mv_web/components/sort_header_component_test.exs b/test/mv_web/components/sort_header_component_test.exs
index e199635..6d23ab4 100644
--- a/test/mv_web/components/sort_header_component_test.exs
+++ b/test/mv_web/components/sort_header_component_test.exs
@@ -24,7 +24,6 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
:house_number,
:postal_code,
:city,
- :phone_number,
:join_date
]
@@ -101,7 +100,6 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
assert has_element?(view, "[data-testid='street'] .opacity-40")
assert has_element?(view, "[data-testid='house_number'] .opacity-40")
assert has_element?(view, "[data-testid='postal_code'] .opacity-40")
- assert has_element?(view, "[data-testid='phone_number'] .opacity-40")
assert has_element?(view, "[data-testid='join_date'] .opacity-40")
end
diff --git a/test/mv_web/helpers/member_helpers_test.exs b/test/mv_web/helpers/member_helpers_test.exs
new file mode 100644
index 0000000..7a11235
--- /dev/null
+++ b/test/mv_web/helpers/member_helpers_test.exs
@@ -0,0 +1,141 @@
+defmodule MvWeb.Helpers.MemberHelpersTest do
+ @moduledoc """
+ Tests for the display_name/1 helper function in MemberHelpers.
+ """
+ use Mv.DataCase, async: true
+
+ alias Mv.Membership.Member
+ alias MvWeb.Helpers.MemberHelpers
+
+ 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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.display_name(member) == "Doe"
+ end
+ end
+end
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..7a11235
--- /dev/null
+++ b/test/mv_web/member_live/index_display_name_test.exs
@@ -0,0 +1,141 @@
+defmodule MvWeb.Helpers.MemberHelpersTest do
+ @moduledoc """
+ Tests for the display_name/1 helper function in MemberHelpers.
+ """
+ use Mv.DataCase, async: true
+
+ alias Mv.Membership.Member
+ alias MvWeb.Helpers.MemberHelpers
+
+ 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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.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 MemberHelpers.display_name(member) == "Doe"
+ end
+ end
+end
diff --git a/test/mv_web/member_live/index_member_fields_display_test.exs b/test/mv_web/member_live/index_member_fields_display_test.exs
index 6b4f50c..c6fd39f 100644
--- a/test/mv_web/member_live/index_member_fields_display_test.exs
+++ b/test/mv_web/member_live/index_member_fields_display_test.exs
@@ -16,7 +16,6 @@ defmodule MvWeb.MemberLive.IndexMemberFieldsDisplayTest do
house_number: "123",
postal_code: "12345",
city: "Berlin",
- phone_number: "+49123456789",
join_date: ~D[2020-01-15]
})
|> Ash.create()
diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs
index d4f5644..acca9bf 100644
--- a/test/mv_web/member_live/index_test.exs
+++ b/test/mv_web/member_live/index_test.exs
@@ -121,7 +121,6 @@ defmodule MvWeb.MemberLive.IndexTest do
:house_number,
:postal_code,
:city,
- :phone_number,
:join_date
]