feat: Datafields page, merge fee types into membership_fee_settings, sidebar

- Add /admin/datafields (DatafieldsLive) for member and custom field config
- Remove Memberdata block from GlobalSettingsLive
- Router: drop /membership_fee_types, add new_fee_type and edit_fee_type under membership_fee_settings
- MembershipFeeSettingsLive: fee types table, collapsible examples; Index links updated
- PagePaths: admin_datafields, admin_import; remove membership_fee_types
- Sidebar: order and labels (Basic settings, Datafields, Membership fee settings, Import, Users, Roles)
- Gettext: German translations for sidebar and OIDC
- Tests: datafields and fee routes, permission and form tests updated
This commit is contained in:
Moritz 2026-02-24 13:55:33 +01:00
parent 8edbbac95f
commit 62b37b9aa2
Signed by: moritz
GPG key ID: 1020A035E5DD0824
18 changed files with 886 additions and 251 deletions

View file

@ -54,7 +54,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
# Create custom field value
create_custom_field_value(member, custom_field, "test")
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
# Click delete button - find the delete link within the component
view
@ -80,7 +80,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
create_custom_field_value(member1, custom_field, "test1")
create_custom_field_value(member2, custom_field, "test2")
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
view
|> element("#custom-fields-component a", "Delete")
@ -93,7 +93,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "shows 0 members for custom field without values", %{conn: conn} do
{:ok, _custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
view
|> element("#custom-fields-component a", "Delete")
@ -108,7 +108,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "updates confirmation state when typing", %{conn: conn} do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
view
|> element("#custom-fields-component a", "Delete")
@ -126,7 +126,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "delete button is disabled when slug doesn't match", %{conn: conn} do
{:ok, _custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
view
|> element("#custom-fields-component a", "Delete")
@ -148,7 +148,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, custom_field_value} = create_custom_field_value(member, custom_field, "test")
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
# Open modal
view
@ -185,7 +185,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
} do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
view
|> element("#custom-fields-component a", "Delete")
@ -209,7 +209,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
test "closes modal without deleting", %{conn: conn} do
{:ok, custom_field} = create_custom_field("test_field", :string)
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
view
|> element("#custom-fields-component a", "Delete")
@ -234,7 +234,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
describe "create custom field" do
test "submitting new data field form creates custom field and shows success", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/settings")
{:ok, view, _html} = live(conn, ~p"/admin/datafields")
# Open "New Data Field" form
view

View file

@ -64,21 +64,5 @@ defmodule MvWeb.GlobalSettingsLiveTest do
assert html =~ "must be present"
end
test "displays Memberdata section", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
assert html =~ "Memberdata" or html =~ "Member Data"
end
test "displays flash message after member field visibility update", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/settings")
# Simulate member field visibility update
send(view.pid, {:member_field_visibility_updated})
# Check for flash message
assert render(view) =~ "updated" or render(view) =~ "success"
end
end
end

View file

@ -23,7 +23,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
describe "rendering" do
test "renders all member fields from Constants", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# Check that all member fields are displayed
member_fields = Mv.Constants.member_fields()
@ -36,7 +36,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
end
test "displays show_in_overview status as badge", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# Should have "Show in overview" column header
assert html =~ "Show in overview" or html =~ "Show in Overview"
@ -46,7 +46,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
end
test "displays required status column", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# Should have "Required" column; email is always required
assert html =~ "Required" or html =~ "required"
@ -59,7 +59,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
{:ok, _updated} =
Membership.update_settings(settings, %{member_field_visibility: %{}})
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# All fields should show as visible (Yes) by default
# Check for "Yes" badge or similar indicator
@ -74,7 +74,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
{:ok, _updated} =
Membership.update_member_field_visibility(settings, visibility_config)
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# Street and house_number should show as hidden (No)
# Other fields should show as visible (Yes)
@ -102,7 +102,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
end
test "marks email as required (always from settings)", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# Email is always required
assert html =~ "email" or html =~ "Email"
@ -119,7 +119,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
required: true
)
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# First name row should show Required (and Optional for others)
assert html =~ "First name" or html =~ "first_name"
@ -127,7 +127,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponentTest do
end
test "optional fields show Optional when not required in settings", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/settings")
{:ok, _view, html} = live(conn, ~p"/admin/datafields")
# Email is required; other fields default to optional
assert html =~ "Optional"

View file

@ -11,7 +11,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
require Ash.Query
setup %{conn: conn} do
# User must have admin role (or normal_user) to access /membership_fee_types pages
# User must have admin role (or normal_user) to access /membership_fee_settings pages
user = Mv.Fixtures.user_with_role_fixture("admin")
authenticated_conn = conn_with_password_user(conn, user)
%{conn: authenticated_conn, user: user}
@ -51,7 +51,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
describe "create form" do
test "creates new membership fee type", %{conn: conn, user: user} do
{:ok, view, _html} = live(conn, "/membership_fee_types/new")
{:ok, view, _html} = live(conn, "/membership_fee_settings/new_fee_type")
form_data = %{
"membership_fee_type[name]" => "New Type",
@ -65,7 +65,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
|> form("#membership-fee-type-form", form_data)
|> render_submit()
assert to == "/membership_fee_types"
assert to == "/membership_fee_settings"
# Verify type was created (use actor so read is authorized)
type =
@ -79,7 +79,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
end
test "interval field is editable on create", %{conn: conn} do
{:ok, _view, html} = live(conn, "/membership_fee_types/new")
{:ok, _view, html} = live(conn, "/membership_fee_settings/new_fee_type")
# Interval field should be editable (not disabled)
refute html =~ "disabled" || html =~ "readonly"
@ -90,7 +90,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
test "loads existing type data", %{conn: conn} do
fee_type = create_fee_type(%{name: "Existing Type", amount: Decimal.new("60.00")})
{:ok, _view, html} = live(conn, "/membership_fee_types/#{fee_type.id}/edit")
{:ok, _view, html} = live(conn, "/membership_fee_settings/#{fee_type.id}/edit_fee_type")
assert html =~ "Existing Type"
assert html =~ "60" || html =~ "60,00"
@ -99,7 +99,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
test "interval field is grayed out on edit", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly})
{:ok, _view, html} = live(conn, "/membership_fee_types/#{fee_type.id}/edit")
{:ok, _view, html} = live(conn, "/membership_fee_settings/#{fee_type.id}/edit_fee_type")
# Interval field should be disabled
assert html =~ "disabled" || html =~ "readonly"
@ -109,7 +109,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
fee_type = create_fee_type(%{amount: Decimal.new("50.00")})
create_member(%{membership_fee_type_id: fee_type.id})
{:ok, view, _html} = live(conn, "/membership_fee_types/#{fee_type.id}/edit")
{:ok, view, _html} = live(conn, "/membership_fee_settings/#{fee_type.id}/edit_fee_type")
# Change amount
view
@ -129,7 +129,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
create_member(%{membership_fee_type_id: fee_type.id})
end)
{:ok, view, _html} = live(conn, "/membership_fee_types/#{fee_type.id}/edit")
{:ok, view, _html} = live(conn, "/membership_fee_settings/#{fee_type.id}/edit_fee_type")
# Change amount
html =
@ -144,7 +144,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
test "amount change can be confirmed", %{conn: conn, user: user} do
fee_type = create_fee_type(%{amount: Decimal.new("50.00")})
{:ok, view, _html} = live(conn, "/membership_fee_types/#{fee_type.id}/edit")
{:ok, view, _html} = live(conn, "/membership_fee_settings/#{fee_type.id}/edit_fee_type")
# Change amount and confirm
view
@ -173,7 +173,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
test "amount change can be cancelled", %{conn: conn, user: user} do
fee_type = create_fee_type(%{amount: Decimal.new("50.00")})
{:ok, view, _html} = live(conn, "/membership_fee_types/#{fee_type.id}/edit")
{:ok, view, _html} = live(conn, "/membership_fee_settings/#{fee_type.id}/edit_fee_type")
# Change amount and cancel
view
@ -195,7 +195,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
end
test "validation errors display correctly", %{conn: conn} do
{:ok, view, _html} = live(conn, "/membership_fee_types/new")
{:ok, view, _html} = live(conn, "/membership_fee_settings/new_fee_type")
# Submit with invalid data
html =
@ -214,7 +214,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
describe "permissions" do
test "only admin can access", %{conn: conn} do
# This test assumes non-admin users cannot access
{:ok, _view, html} = live(conn, "/membership_fee_types/new")
{:ok, _view, html} = live(conn, "/membership_fee_settings/new_fee_type")
# Should show the form (admin user in setup)
assert html =~ "Membership Fee Type" || html =~ "Beitragsart"

View file

@ -60,7 +60,7 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
admin_user
)
{:ok, _view, html} = live(conn, "/membership_fee_types")
{:ok, _view, html} = live(conn, "/membership_fee_settings")
assert html =~ "Regular"
assert html =~ "Reduced"
@ -77,33 +77,33 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
create_member(%{membership_fee_type_id: fee_type.id}, admin_user)
end)
{:ok, _view, html} = live(conn, "/membership_fee_types")
{:ok, _view, html} = live(conn, "/membership_fee_settings")
assert html =~ "3" || html =~ "Members" || html =~ "Mitglieder"
end
test "create button navigates to form", %{conn: conn} do
{:ok, view, _html} = live(conn, "/membership_fee_types")
{:ok, view, _html} = live(conn, "/membership_fee_settings")
{:error, {:live_redirect, %{to: to}}} =
view
|> element("a[href='/membership_fee_types/new']")
|> element("a[href='/membership_fee_settings/new_fee_type']")
|> render_click()
assert to == "/membership_fee_types/new"
assert to == "/membership_fee_settings/new_fee_type"
end
test "edit button per row navigates to edit form", %{conn: conn, current_user: admin_user} do
fee_type = create_fee_type(%{interval: :yearly}, admin_user)
{:ok, view, _html} = live(conn, "/membership_fee_types")
{:ok, view, _html} = live(conn, "/membership_fee_settings")
{:error, {:live_redirect, %{to: to}}} =
view
|> element("a[href='/membership_fee_types/#{fee_type.id}/edit']")
|> element("a[href='/membership_fee_settings/#{fee_type.id}/edit_fee_type']")
|> render_click()
assert to == "/membership_fee_types/#{fee_type.id}/edit"
assert to == "/membership_fee_settings/#{fee_type.id}/edit_fee_type"
end
end
@ -112,7 +112,7 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
fee_type = create_fee_type(%{interval: :yearly}, admin_user)
create_member(%{membership_fee_type_id: fee_type.id}, admin_user)
{:ok, _view, html} = live(conn, "/membership_fee_types")
{:ok, _view, html} = live(conn, "/membership_fee_settings")
# Delete button should be disabled
assert html =~ "disabled" || html =~ "cursor-not-allowed"
@ -122,7 +122,7 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
fee_type = create_fee_type(%{interval: :yearly}, admin_user)
# No members assigned
{:ok, view, _html} = live(conn, "/membership_fee_types")
{:ok, view, _html} = live(conn, "/membership_fee_settings")
# Delete button should be enabled
view
@ -142,10 +142,11 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
test "only admin can access", %{conn: conn} do
# This test assumes non-admin users cannot access
# Adjust based on actual permission implementation
{:ok, _view, html} = live(conn, "/membership_fee_types")
{:ok, _view, html} = live(conn, "/membership_fee_settings")
# Should show the page (admin user in setup)
assert html =~ "Membership Fee Types" || html =~ "Beitragsarten"
assert html =~ "Membership Fee Settings" || html =~ "Beitragseinstellungen" ||
html =~ "Membership Fee Types"
end
end
end