Implements uneditable type for custom fields closes #198 #433

Merged
carla merged 4 commits from feature/198_edit_custom_fields into main 2026-02-19 10:02:19 +01:00
3 changed files with 43 additions and 49 deletions
Showing only changes of commit 0333f9e722 - Show all commits

View file

@ -26,7 +26,6 @@ defmodule MvWeb.Components.SortHeaderComponent do
class="btn btn-ghost select-none" class="btn btn-ghost select-none"
phx-click="sort" phx-click="sort"
phx-value-field={@field} phx-value-field={@field}
phx-target={@myself}
data-testid={@field} data-testid={@field}
> >
{@label} {@label}
@ -43,12 +42,6 @@ defmodule MvWeb.Components.SortHeaderComponent do
""" """
end end
@impl true
def handle_event("sort", %{"field" => field_str}, socket) do
send(self(), {:sort, field_str})
{:noreply, socket}
end
# ------------------------------------------------- # -------------------------------------------------
# Hilfsfunktionen für ARIA Attribute & Icon SVG # Hilfsfunktionen für ARIA Attribute & Icon SVG
# ------------------------------------------------- # -------------------------------------------------

View file

@ -163,6 +163,7 @@ defmodule MvWeb.MemberLive.Index do
- `"delete"` - Removes a member from the database - `"delete"` - Removes a member from the database
- `"select_member"` - Toggles individual member selection - `"select_member"` - Toggles individual member selection
- `"select_all"` - Toggles selection of all visible members - `"select_all"` - Toggles selection of all visible members
- `"sort"` - Sort event from SortHeaderComponent. Updates sort field/order and syncs URL
""" """
@impl true @impl true
def handle_event("delete", %{"id" => id}, socket) do def handle_event("delete", %{"id" => id}, socket) do
@ -305,6 +306,46 @@ defmodule MvWeb.MemberLive.Index do
end end
end end
@impl true
def handle_event("sort", %{"field" => field_str}, socket) do
# Handle both atom and string field names (for custom fields)
field =
try do
String.to_existing_atom(field_str)
rescue
ArgumentError -> field_str
end
{new_field, new_order} = determine_new_sort(field, socket)
old_field = socket.assigns.sort_field
socket =
socket
|> assign(:sort_field, new_field)
|> assign(:sort_order, new_order)
|> update_sort_components(old_field, new_field, new_order)
|> load_members()
|> update_selection_assigns()
# URL sync - push_patch happens synchronously in the event handler
query_params =
build_query_params(
socket.assigns.query,
export_sort_field(socket.assigns.sort_field),
export_sort_order(socket.assigns.sort_order),
socket.assigns.cycle_status_filter,
socket.assigns[:group_filters],
socket.assigns.show_current_cycle,
socket.assigns.boolean_custom_field_filters
)
|> maybe_add_field_selection(
socket.assigns[:user_field_selection],
socket.assigns[:fields_in_url?] || false
)
{:noreply, push_patch(socket, to: ~p"/members?#{query_params}", replace: true)}
end
# Helper to format errors for display # Helper to format errors for display
defp format_error(%Ash.Error.Invalid{errors: errors}) do defp format_error(%Ash.Error.Invalid{errors: errors}) do
error_messages = error_messages =
@ -329,50 +370,10 @@ defmodule MvWeb.MemberLive.Index do
Handles messages from child components. Handles messages from child components.
## Supported messages: ## Supported messages:
- `{:sort, field}` - Sort event from SortHeaderComponent. Updates sort field/order and syncs URL
- `{:search_changed, query}` - Search event from SearchBarComponent. Filters members and syncs URL - `{:search_changed, query}` - Search event from SearchBarComponent. Filters members and syncs URL
- `{:field_toggled, field, visible}` - Field toggle event from FieldVisibilityDropdownComponent - `{:field_toggled, field, visible}` - Field toggle event from FieldVisibilityDropdownComponent
- `{:fields_selected, selection}` - Select all/deselect all event from FieldVisibilityDropdownComponent - `{:fields_selected, selection}` - Select all/deselect all event from FieldVisibilityDropdownComponent
""" """
@impl true
def handle_info({:sort, field_str}, socket) do
# Handle both atom and string field names (for custom fields)
field =
try do
String.to_existing_atom(field_str)
rescue
ArgumentError -> field_str
end
{new_field, new_order} = determine_new_sort(field, socket)
old_field = socket.assigns.sort_field
socket =
socket
|> assign(:sort_field, new_field)
|> assign(:sort_order, new_order)
|> update_sort_components(old_field, new_field, new_order)
|> load_members()
|> update_selection_assigns()
# URL sync
query_params =
build_query_params(
socket.assigns.query,
export_sort_field(socket.assigns.sort_field),
export_sort_order(socket.assigns.sort_order),
socket.assigns.cycle_status_filter,
socket.assigns[:group_filters],
socket.assigns.show_current_cycle,
socket.assigns.boolean_custom_field_filters
)
|> maybe_add_field_selection(
socket.assigns[:user_field_selection],
socket.assigns[:fields_in_url?] || false
)
{:noreply, push_patch(socket, to: ~p"/members?#{query_params}", replace: true)}
end
@impl true @impl true
def handle_info({:search_changed, q}, socket) do def handle_info({:search_changed, q}, socket) do

View file

@ -223,7 +223,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
end end
describe "component behavior" do describe "component behavior" do
test "clicking sends sort message to parent", %{conn: conn} do test "clicking triggers sort event on parent LiveView", %{conn: conn} do
conn = conn_with_oidc_user(conn) conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members") {:ok, view, _html} = live(conn, "/members")
@ -232,7 +232,7 @@ defmodule MvWeb.Components.SortHeaderComponentTest do
|> element("button[phx-value-field='first_name']") |> element("button[phx-value-field='first_name']")
|> render_click() |> render_click()
# The component should send a message to the parent LiveView # The component triggers a "sort" event on the parent LiveView
# This is tested indirectly through the URL change in integration tests # This is tested indirectly through the URL change in integration tests
end end