defmodule MvWeb.MemberLive.Show do @moduledoc """ LiveView for displaying a single member's details. ## Features - Display all member information in grouped sections - Tab navigation for future features (Payments) - Show custom field values with type-based formatting - Navigate to edit form - Return to member list ## Sections - Personal Data: Name, address, contact information, membership dates, notes - Custom Fields: Dynamic fields in uniform grid layout (sorted by name) - Payment Data: Mockup section with placeholder data ## Navigation - Back to member list - Edit member (with return_to parameter for back navigation) """ use MvWeb, :live_view import Ash.Query @impl true def render(assigns) do ~H""" <%!-- Header with Back button, Name, and Edit button --%>
<.button navigate={~p"/members"} aria-label={gettext("Back to members list")}> <.icon name="hero-arrow-left" class="size-4" /> {gettext("Back")}

{@member.first_name} {@member.last_name}

<.button variant="primary" navigate={~p"/members/#{@member}/edit?return_to=show"}> {gettext("Edit Member")}
<%!-- Tab Navigation --%>
<%!-- Personal Data and Custom Fields Row --%>
<%!-- Personal Data Section --%>
<.section_box title={gettext("Personal Data")}>
<%!-- Name Row --%>
<.data_field label={gettext("First Name")} value={@member.first_name} class="w-48" /> <.data_field label={gettext("Last Name")} value={@member.last_name} class="w-48" />
<%!-- Address --%>
<.data_field label={gettext("Address")} value={format_address(@member)} />
<%!-- Email --%>
<.data_field label={gettext("Email")}> {@member.email}
<%!-- Phone --%>
<.data_field label={gettext("Phone")} value={@member.phone_number} />
<%!-- Membership Dates Row --%>
<.data_field label={gettext("Join Date")} value={format_date(@member.join_date)} class="w-28" /> <.data_field label={gettext("Exit Date")} value={format_date(@member.exit_date)} class="w-28" />
<%!-- Linked User --%>
<.data_field label={gettext("Linked User")}> <%= if @member.user do %> <.link navigate={~p"/users/#{@member.user}"} class="text-blue-700 hover:text-blue-800 underline inline-flex items-center gap-1" > <.icon name="hero-user" class="size-4" /> {@member.user.email} <% else %> {gettext("No user linked")} <% end %>
<%!-- Notes --%> <%= if @member.notes && String.trim(@member.notes) != "" do %>
<.data_field label={gettext("Notes")}>

{@member.notes}

<% end %>
<%!-- Custom Fields Section --%> <%= if Enum.any?(@member.custom_field_values) do %>
<.section_box title={gettext("Custom Fields")}>
<%= for cfv <- sort_custom_field_values(@member.custom_field_values) do %> <% custom_field = cfv.custom_field %> <% value_type = custom_field && custom_field.value_type %> <.data_field label={custom_field && custom_field.name}> {format_custom_field_value(cfv.value, value_type)} <% end %>
<% end %>
<%!-- Payment Data Section (Mockup) --%>
<.section_box title={gettext("Payment Data")}>
<.data_field label={gettext("Contribution")} value="72 €" class="w-24" /> <.data_field label={gettext("Payment Cycle")} value={gettext("monthly")} class="w-28" /> <.data_field label={gettext("Paid")} class="w-24"> <%= if @member.paid do %> {gettext("Paid")} <% else %> {gettext("Pending")} <% end %>
""" end @impl true def mount(_params, _session, socket) do {:ok, socket} end @impl true def handle_params(%{"id" => id}, _, socket) do query = Mv.Membership.Member |> filter(id == ^id) |> load([:user, custom_field_values: [:custom_field]]) member = Ash.read_one!(query) {:noreply, socket |> assign(:page_title, page_title(socket.assigns.live_action)) |> assign(:member, member)} end defp page_title(:show), do: gettext("Show Member") defp page_title(:edit), do: gettext("Edit Member") # ----------------------------------------------------------------- # Helper Components # ----------------------------------------------------------------- # Renders a section box with border and title. attr :title, :string, required: true slot :inner_block, required: true defp section_box(assigns) do ~H"""

{@title}

{render_slot(@inner_block)}
""" end # Renders a labeled data field. attr :label, :string, required: true attr :value, :string, default: nil attr :class, :string, default: "" slot :inner_block defp data_field(assigns) do ~H"""
{@label}
<%= if @inner_block != [] do %> {render_slot(@inner_block)} <% else %> {display_value(@value)} <% end %>
""" end # ----------------------------------------------------------------- # Helper Functions # ----------------------------------------------------------------- defp display_value(nil), do: "" defp display_value(""), do: "" defp display_value(value), do: value defp format_address(member) do street_part = [member.street, member.house_number] |> Enum.filter(&(&1 && &1 != "")) |> Enum.join(" ") city_part = [member.postal_code, member.city] |> Enum.filter(&(&1 && &1 != "")) |> Enum.join(" ") [street_part, city_part] |> Enum.filter(&(&1 != "")) |> Enum.join(", ") |> case do "" -> nil address -> address end end defp format_date(nil), do: nil defp format_date(%Date{} = date) do Calendar.strftime(date, "%d.%m.%Y") end defp format_date(date), do: to_string(date) # Sorts custom field values by custom field name defp sort_custom_field_values(custom_field_values) do Enum.sort_by(custom_field_values, fn cfv -> (cfv.custom_field && cfv.custom_field.name) || "" end) end # Formats custom field value based on type defp format_custom_field_value(%Ash.Union{value: value, type: type}, _expected_type) do format_custom_field_value(value, type) end defp format_custom_field_value(nil, _type), do: "—" defp format_custom_field_value(value, :boolean) when is_boolean(value) do if value, do: gettext("Yes"), else: gettext("No") end defp format_custom_field_value(%Date{} = date, :date) do Calendar.strftime(date, "%d.%m.%Y") end defp format_custom_field_value(value, :email) when is_binary(value) do assigns = %{email: value} ~H""" {@email} """ end defp format_custom_field_value(value, :integer) when is_integer(value) do Integer.to_string(value) end defp format_custom_field_value(value, _type) when is_binary(value) do if String.trim(value) == "", do: "—", else: value end defp format_custom_field_value(value, _type), do: to_string(value) end