fix: datafield edit view was shown alongside othe relements
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
carla 2026-02-26 12:37:52 +01:00
parent faf80bfb4b
commit 9751525a0c
8 changed files with 85 additions and 41 deletions

View file

@ -19,8 +19,8 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
assigns = assign(assigns, :field_type_label, &MvWeb.Translations.FieldTypes.label/1)
~H"""
<div id={@id} class="mt-8">
<div class="flex">
<div id={@id}>
<div :if={!@show_form} class="flex">
<p class="text-sm text-base-content/70">
{gettext("These will appear in addition to other data when adding new members.")}
</p>
@ -118,15 +118,15 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
<div>
<p class="font-semibold">
{ngettext(
"%{count} member has a value assigned for this custom field.",
"%{count} members have values assigned for this custom field.",
"%{count} member has a value assigned for this datafield.",
"%{count} members have values assigned for this datafield.",
@custom_field_to_delete.assigned_members_count,
count: @custom_field_to_delete.assigned_members_count
)}
</p>
<p class="mt-2 text-sm">
{gettext(
"All custom field values will be permanently deleted when you delete this custom field."
"All datafield values will be permanently deleted when you delete this datfield."
)}
</p>
</div>
@ -192,8 +192,8 @@ defmodule MvWeb.CustomFieldLive.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)
# Use socket state so send_update(open_delete_for_id: ...) does not trigger false "form closed"
previous_show_form = socket.assigns[:show_form] || false
# If show_form is explicitly provided in assigns, reset editing state
socket =
@ -205,13 +205,6 @@ defmodule MvWeb.CustomFieldLive.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
# Get actor from assigns or fall back to socket assigns
actor = Map.get(assigns, :actor, socket.assigns[:actor])
@ -246,6 +239,13 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do
|> assign(:open_delete_for_id, nil)
end
# Detect form closed only from final socket state (not from assigns alone)
current_show_form = socket.assigns[:show_form] || false
if previous_show_form and not current_show_form do
send(self(), {:editing_section_changed, nil})
end
{:ok, socket}
end

View file

@ -29,27 +29,47 @@ defmodule MvWeb.DatafieldsLive do
<.header>
{gettext("Datafields")}
<:subtitle>
{gettext("Configure which data you want to save for your members. Define individual datafields.")}
{gettext(
"Configure which data you want to save for your members. Define individual datafields."
)}
</:subtitle>
</.header>
<.form_section title={gettext("Member fields")}>
<%!-- Overview: both sections with form_section wrappers --%>
<div :if={@active_editing_section == nil} class="mt-6 space-y-6">
<.form_section title={gettext("Personal Data")}>
<.live_component
module={MvWeb.MemberFieldLive.IndexComponent}
id="member-fields-component"
settings={@settings}
/>
</.form_section>
<.form_section title={gettext("Individual Datafields")}>
<.live_component
module={MvWeb.CustomFieldLive.IndexComponent}
id="custom-fields-component"
actor={@current_user}
/>
</.form_section>
</div>
<%!-- Edit mode: only the active section, no section title/card wrapper --%>
<div :if={@active_editing_section == :member_fields} class="mt-6">
<.live_component
:if={@active_editing_section != :custom_fields}
module={MvWeb.MemberFieldLive.IndexComponent}
id="member-fields-component"
settings={@settings}
/>
</.form_section>
</div>
<.form_section title={gettext("Custom fields")}>
<div :if={@active_editing_section == :custom_fields} class="mt-6">
<.live_component
:if={@active_editing_section != :member_fields}
module={MvWeb.CustomFieldLive.IndexComponent}
id="custom-fields-component"
actor={@current_user}
/>
</.form_section>
</div>
</Layouts.app>
"""
end

View file

@ -25,7 +25,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do
~H"""
<div id={@id}>
<p class="text-sm text-base-content/70 mb-4">
<p :if={!@show_form} class="text-sm text-base-content/70 mb-4">
{gettext(
"These fields are neccessary for MILA to handle member identification and payment calculations in the future. Thus you cannot delete these fields but hide them in the member overview."
)}
@ -100,8 +100,8 @@ 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)
# Use socket state so send_update(show_form: false) is the only trigger for "form closed"
previous_show_form = socket.assigns[:show_form] || false
# If show_form is explicitly provided in assigns, reset editing state
socket =
@ -113,20 +113,22 @@ 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)
socket =
socket
|> assign(assigns)
|> assign_new(:settings, fn -> get_settings() end)
|> assign_new(:show_form, fn -> false end)
|> assign_new(:form_id, fn -> "member-field-form-new" end)
|> assign_new(:editing_member_field, fn -> nil end)
if previous_show_form and not new_show_form do
# Detect form closed only from final socket state (not from assigns alone)
current_show_form = socket.assigns[:show_form] || false
if previous_show_form and not current_show_form do
send(self(), {:editing_section_changed, nil})
end
{:ok,
socket
|> assign(assigns)
|> assign_new(:settings, fn -> get_settings() end)
|> assign_new(:show_form, fn -> false end)
|> assign_new(:form_id, fn -> "member-field-form-new" end)
|> assign_new(:editing_member_field, fn -> nil end)}
{:ok, socket}
end
@impl true

View file

@ -79,7 +79,7 @@ defmodule MvWeb.UserLive.Form do
/>
</div>
<% end %>
<!-- Password Section -->
<div class="mt-6">
<label class="flex items-center space-x-2">
@ -116,7 +116,7 @@ defmodule MvWeb.UserLive.Form do
required
autocomplete="new-password"
/>
<!-- Only show password confirmation for new users (register_with_password) -->
<%= if !@user do %>
<.input
@ -167,7 +167,7 @@ defmodule MvWeb.UserLive.Form do
<% end %>
<% end %>
</div>
<!-- Member Linking Section (admin only: only admins can link/unlink users to members) -->
<%= if @can_manage_member_linking do %>
<div class="mt-6">

View file

@ -71,6 +71,10 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
# Modal should be visible
assert has_element?(view, "#delete-custom-field-modal")
# Edit mode: section titles must not reappear when modal opens (regression)
refute has_element?(view, "h2", "Member fields")
refute has_element?(view, "h2", "Custom fields")
# Should show correct member count (1 member)
assert render(view) =~ "1 member has a value assigned for this custom field"

View file

@ -83,6 +83,21 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
end
end
describe "edit mode visibility" do
test "clicking member field row shows only form, no section titles", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
# Row click is on the first td (no col_click); click that cell to open edit form
view
|> element("tr#member_field-first_name td:first-child")
|> render_click()
assert has_element?(view, "#member-field-form-first_name")
refute has_element?(view, "h2", "Custom fields")
refute has_element?(view, "h2", "Member fields")
end
end
describe "required fields" do
setup do
{:ok, settings} = Membership.get_settings()

View file

@ -29,9 +29,8 @@ defmodule MvWeb.StatisticsLiveTest do
test "page shows overview of all relevant years without year selector", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/statistics")
# No year dropdown: single select for year should not be present as main control
assert html =~ "Overview" or html =~ "overview"
# table header or legend
# Page shows multi-year data (member numbers by year) and year column; no single-year selector as main control
assert html =~ "Member numbers by year"
assert html =~ "Year"
end

View file

@ -123,13 +123,17 @@ defmodule MvWeb.UserLive.IndexTest do
{:ok, index_view, _html} = live(conn, "/users")
assert render(index_view) =~ "delete-me@example.com"
# Navigate to user show and trigger delete from Danger zone
# Navigate to user show, open delete modal, then confirm in modal (WCAG modal pattern)
{:ok, show_view, _html} = live(conn, "/users/#{user.id}")
show_view
|> element("[data-testid=user-delete]")
|> render_click()
show_view
|> element("#delete-user-modal button", "Delete")
|> render_click()
# Should redirect to index
assert_redirect(show_view, "/users")