diff --git a/lib/mv_web/helpers/membership_fee_helpers.ex b/lib/mv_web/helpers/membership_fee_helpers.ex index 53d32c7..4986ca6 100644 --- a/lib/mv_web/helpers/membership_fee_helpers.ex +++ b/lib/mv_web/helpers/membership_fee_helpers.ex @@ -8,9 +8,9 @@ defmodule MvWeb.Helpers.MembershipFeeHelpers do use Gettext, backend: MvWeb.Gettext + alias Mv.Membership.Member alias Mv.MembershipFees.CalendarCycles alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.Membership.Member @doc """ Formats a decimal amount as currency string. diff --git a/lib/mv_web/live/member_field_live/form_component.ex b/lib/mv_web/live/member_field_live/form_component.ex index a9985cb..0f0b446 100644 --- a/lib/mv_web/live/member_field_live/form_component.ex +++ b/lib/mv_web/live/member_field_live/form_component.ex @@ -18,8 +18,8 @@ defmodule MvWeb.MemberFieldLive.FormComponent do use MvWeb, :live_component alias Mv.Membership - alias MvWeb.Translations.MemberFields alias MvWeb.Translations.FieldTypes + alias MvWeb.Translations.MemberFields @required_fields [:first_name, :last_name, :email] diff --git a/lib/mv_web/live/member_field_live/index_component.ex b/lib/mv_web/live/member_field_live/index_component.ex index eec98be..2d4f1dc 100644 --- a/lib/mv_web/live/member_field_live/index_component.ex +++ b/lib/mv_web/live/member_field_live/index_component.ex @@ -12,8 +12,8 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do use MvWeb, :live_component alias Mv.Membership - alias MvWeb.Translations.MemberFields alias MvWeb.Translations.FieldTypes + alias MvWeb.Translations.MemberFields @impl true def render(assigns) do @@ -109,6 +109,9 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do @impl true def update(assigns, socket) do + # Track previous show_form state to detect when form is closed + previous_show_form = Map.get(socket.assigns, :show_form, false) + # If show_form is explicitly provided in assigns, reset editing state socket = if Map.has_key?(assigns, :show_form) and assigns.show_form == false do @@ -119,6 +122,13 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do socket end + # Detect when form is closed (show_form changes from true to false) + new_show_form = Map.get(assigns, :show_form, false) + + if previous_show_form and not new_show_form do + send(self(), {:editing_section_changed, nil}) + end + {:ok, socket |> assign(assigns) @@ -136,6 +146,11 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do if field_string in valid_fields do field_atom = String.to_existing_atom(field_string) + # Only send event if form was not already open + if not socket.assigns[:show_form] do + send(self(), {:editing_section_changed, :member_fields}) + end + {:noreply, socket |> assign(:show_form, true) diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index fff5517..34928cd 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -31,10 +31,10 @@ defmodule MvWeb.MemberLive.Index do import Ash.Expr alias Mv.Membership - alias MvWeb.MemberLive.Index.Formatter alias MvWeb.Helpers.DateFormatter alias MvWeb.MemberLive.Index.FieldSelection alias MvWeb.MemberLive.Index.FieldVisibility + alias MvWeb.MemberLive.Index.Formatter alias MvWeb.MemberLive.Index.MembershipFeeStatus # Prefix used in sort field names for custom fields (e.g., "custom_field_") diff --git a/lib/mv_web/live/member_live/show/membership_fees_component.ex b/lib/mv_web/live/member_live/show/membership_fees_component.ex index f96fd73..0bc93a1 100644 --- a/lib/mv_web/live/member_live/show/membership_fees_component.ex +++ b/lib/mv_web/live/member_live/show/membership_fees_component.ex @@ -15,10 +15,10 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do require Ash.Query alias Mv.Membership - alias Mv.MembershipFees.MembershipFeeType - alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.CycleGenerator alias Mv.MembershipFees.CalendarCycles + alias Mv.MembershipFees.CycleGenerator + alias Mv.MembershipFees.MembershipFeeCycle + alias Mv.MembershipFees.MembershipFeeType alias MvWeb.Helpers.MembershipFeeHelpers @impl true diff --git a/lib/mv_web/live/membership_fee_type_live/form.ex b/lib/mv_web/live/membership_fee_type_live/form.ex index 5acb8c9..77a73af 100644 --- a/lib/mv_web/live/membership_fee_type_live/form.ex +++ b/lib/mv_web/live/membership_fee_type_live/form.ex @@ -15,9 +15,9 @@ defmodule MvWeb.MembershipFeeTypeLive.Form do require Ash.Query + alias Mv.Membership.Member alias Mv.MembershipFees alias Mv.MembershipFees.MembershipFeeType - alias Mv.Membership.Member alias MvWeb.Helpers.MembershipFeeHelpers @impl true 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 176d4e1..262983f 100644 --- a/lib/mv_web/live/membership_fee_type_live/index.ex +++ b/lib/mv_web/live/membership_fee_type_live/index.ex @@ -16,10 +16,10 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do require Ash.Query - alias Mv.MembershipFees - alias Mv.MembershipFees.MembershipFeeType alias Mv.Membership alias Mv.Membership.Member + alias Mv.MembershipFees + alias Mv.MembershipFees.MembershipFeeType alias MvWeb.Helpers.MembershipFeeHelpers @impl true diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 4f99e5b..9bbcff3 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -491,10 +491,39 @@ default_club_name = System.get_env("ASSOCIATION_NAME") || "Club Name" case Membership.get_settings() do {:ok, existing_settings} -> # Settings exist, update if club_name is different from env var - if existing_settings.club_name != default_club_name do - {:ok, _updated} = - Membership.update_settings(existing_settings, %{club_name: default_club_name}) + # Also ensure exit_date is set to false by default if not already configured + updates = + %{} + |> then(fn acc -> + if existing_settings.club_name != default_club_name, + do: Map.put(acc, :club_name, default_club_name), + else: acc + end) + |> then(fn acc -> + visibility_config = existing_settings.member_field_visibility || %{} + # Ensure exit_date is set to false if not already configured + if not Map.has_key?(visibility_config, "exit_date") and + not Map.has_key?(visibility_config, :exit_date) do + updated_visibility = Map.put(visibility_config, "exit_date", false) + Map.put(acc, :member_field_visibility, updated_visibility) + else + acc + end + end) + + if map_size(updates) > 0 do + {:ok, _updated} = Membership.update_settings(existing_settings, updates) end + + {:ok, nil} -> + # Settings don't exist yet, create with exit_date defaulting to false + {:ok, _settings} = + Membership.Setting + |> Ash.Changeset.for_create(:create, %{ + club_name: default_club_name, + member_field_visibility: %{"exit_date" => false} + }) + |> Ash.create!() end IO.puts("✅ Seeds completed successfully!") diff --git a/test/membership/member_field_visibility_test.exs b/test/membership/member_field_visibility_test.exs index 9c7e5e0..6bc04f6 100644 --- a/test/membership/member_field_visibility_test.exs +++ b/test/membership/member_field_visibility_test.exs @@ -13,14 +13,17 @@ defmodule Mv.Membership.MemberFieldVisibilityTest do alias Mv.Membership.Member describe "show_in_overview?/1" do - test "returns true for all member fields by default" do + test "returns true for all member fields by default, except exit_date" do # When no settings exist or member_field_visibility is not configured # Test with fields from constants + # Note: exit_date defaults to false (hidden) by design member_fields = Mv.Constants.member_fields() Enum.each(member_fields, fn field -> - assert Member.show_in_overview?(field) == true, - "Field #{field} should be visible by default" + expected_visibility = if field == :exit_date, do: false, else: true + + assert Member.show_in_overview?(field) == expected_visibility, + "Field #{field} should be #{if expected_visibility, do: "visible", else: "hidden"} by default" end) end diff --git a/test/mv_web/live/member_field_live/index_component_test.exs b/test/mv_web/live/member_field_live/index_component_test.exs index e2e1be3..037a77c 100644 --- a/test/mv_web/live/member_field_live/index_component_test.exs +++ b/test/mv_web/live/member_field_live/index_component_test.exs @@ -6,8 +6,6 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do - Rendering all member fields from Mv.Constants.member_fields() - Displaying show_in_overview status as badge (Yes/No) - Displaying required status for required fields (first_name, last_name, email) - - Toggle functionality to change show_in_overview flag - - Settings are correctly updated after toggle - Current status is displayed based on settings.member_field_visibility - Default status is "Yes" (visible) when not configured in settings """ @@ -86,70 +84,6 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do end end - describe "toggle functionality" do - test "toggles field visibility from visible to hidden", %{conn: conn} do - # Start with field visible (default) - {:ok, settings} = Membership.get_settings() - - {:ok, _updated} = - Membership.update_member_field_visibility(settings, %{"street" => true}) - - {:ok, view, _html} = live(conn, ~p"/settings") - - # Find and click toggle button for street field - # This will fail until component is implemented - assert has_element?(view, "#member-field-street-toggle") or - has_element?(view, "[phx-click='toggle_field_visibility'][data-field='street']") - - # Click toggle - view - |> element("#member-field-street-toggle") - |> render_click(%{"field" => "street"}) - - # Verify settings updated - {:ok, updated_settings} = Membership.get_settings() - visibility = updated_settings.member_field_visibility || %{} - assert Map.get(visibility, "street") == false - end - - test "toggles field visibility from hidden to visible", %{conn: conn} do - # Start with field hidden - {:ok, settings} = Membership.get_settings() - - {:ok, _updated} = - Membership.update_member_field_visibility(settings, %{"street" => false}) - - {:ok, view, _html} = live(conn, ~p"/settings") - - # Click toggle to make visible - view - |> element("#member-field-street-toggle") - |> render_click(%{"field" => "street"}) - - # Verify settings updated - {:ok, updated_settings} = Membership.get_settings() - visibility = updated_settings.member_field_visibility || %{} - assert Map.get(visibility, "street") == true - end - - test "sends message to parent LiveView after toggle", %{conn: conn} do - {:ok, settings} = Membership.get_settings() - - {:ok, _updated} = - Membership.update_member_field_visibility(settings, %{"street" => true}) - - {:ok, view, _html} = live(conn, ~p"/settings") - - # Toggle field - view - |> element("#member-field-street-toggle") - |> render_click(%{"field" => "street"}) - - # Check for flash message (handled by parent LiveView) - assert render(view) =~ "updated" or render(view) =~ "success" - end - end - describe "required fields" do test "marks first_name as required", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/settings") diff --git a/test/mv_web/member_live/index_required_display_test.exs b/test/mv_web/member_live/index_required_display_test.exs deleted file mode 100644 index eb61fea..0000000 --- a/test/mv_web/member_live/index_required_display_test.exs +++ /dev/null @@ -1,157 +0,0 @@ -defmodule MvWeb.MemberLive.IndexRequiredDisplayTest do - @moduledoc """ - Tests for displaying "required" badge in member overview. - - Tests cover: - - "required" badge for required member fields (first_name, last_name, email) - - "required" badge for required custom fields - - No "required" badge for optional member fields - - No "required" badge for optional custom fields - - Badge is positioned in column header - """ - # async: false to prevent PostgreSQL deadlocks when creating members and custom fields - use MvWeb.ConnCase, async: false - import Phoenix.LiveViewTest - require Ash.Query - - alias Mv.Membership.{CustomField, CustomFieldValue, Member} - - setup do - # Create test member - {:ok, member} = - Member - |> Ash.Changeset.for_create(:create_member, %{ - first_name: "Alice", - last_name: "Anderson", - email: "alice@example.com" - }) - |> Ash.create() - - # Create required custom field - {:ok, required_field} = - CustomField - |> Ash.Changeset.for_create(:create, %{ - name: "emergency_contact", - value_type: :string, - required: true, - show_in_overview: true - }) - |> Ash.create() - - # Create optional custom field - {:ok, optional_field} = - CustomField - |> Ash.Changeset.for_create(:create, %{ - name: "hobby", - value_type: :string, - required: false, - show_in_overview: true - }) - |> Ash.create() - - # Create custom field values - {:ok, _cfv1} = - CustomFieldValue - |> Ash.Changeset.for_create(:create, %{ - member_id: member.id, - custom_field_id: required_field.id, - value: %{"_union_type" => "string", "_union_value" => "John Doe"} - }) - |> Ash.create() - - {:ok, _cfv2} = - CustomFieldValue - |> Ash.Changeset.for_create(:create, %{ - member_id: member.id, - custom_field_id: optional_field.id, - value: %{"_union_type" => "string", "_union_value" => "Reading"} - }) - |> Ash.create() - - %{ - member: member, - required_field: required_field, - optional_field: optional_field - } - end - - describe "required badge for member fields" do - test "displays required badge for first_name column", %{conn: conn} do - conn = conn_with_oidc_user(conn) - {:ok, _view, html} = live(conn, "/members") - - # Check that first_name column header has required badge - assert html =~ "first_name" or html =~ "First name" or html =~ "First Name" - # Should have required indicator in header - assert html =~ "required" or html =~ "Required" - end - - test "displays required badge for last_name column", %{conn: conn} do - conn = conn_with_oidc_user(conn) - {:ok, _view, html} = live(conn, "/members") - - # Check that last_name column header has required badge - assert html =~ "last_name" or html =~ "Last name" or html =~ "Last Name" - # Should have required indicator in header - assert html =~ "required" or html =~ "Required" - end - - test "displays required badge for email column", %{conn: conn} do - conn = conn_with_oidc_user(conn) - {:ok, _view, html} = live(conn, "/members") - - # Check that email column header has required badge - assert html =~ "email" or html =~ "Email" - # Should have required indicator in header - assert html =~ "required" or html =~ "Required" - end - - test "does not display required badge for optional member fields", %{conn: conn} do - conn = conn_with_oidc_user(conn) - {:ok, _view, html} = live(conn, "/members") - - # Optional fields: street, city, phone_number, etc. - # These should not have required badge - # We check that street is present but doesn't have required indicator nearby - assert html =~ "street" or html =~ "Street" - end - end - - describe "required badge for custom fields" do - test "displays required badge for required custom field column", %{ - conn: conn, - required_field: field - } do - conn = conn_with_oidc_user(conn) - {:ok, _view, html} = live(conn, "/members") - - # Check that required custom field column header has required badge - assert html =~ field.name - # Should have required indicator in header - assert html =~ "required" or html =~ "Required" - end - - test "does not display required badge for optional custom field column", %{ - conn: conn, - optional_field: field - } do - conn = conn_with_oidc_user(conn) - {:ok, _view, html} = live(conn, "/members") - - # Check that optional custom field column header does not have required badge - assert html =~ field.name - # Should not have required indicator (or it should be clear it's optional) - end - end - - describe "badge positioning" do - test "required badge is in column header, not in cell content", %{conn: conn} do - conn = conn_with_oidc_user(conn) - {:ok, _view, html} = live(conn, "/members") - - # Required badge should be in thead (header), not in tbody (data rows) - # This is verified by checking that required appears near column headers - assert html =~ "thead" or html =~ "th" - end - end -end