refactor: use core components
This commit is contained in:
parent
f0be98316c
commit
b7c93f19cb
26 changed files with 1080 additions and 954 deletions
|
|
@ -30,235 +30,243 @@ defmodule MvWeb.MemberLive.Show do
|
|||
def render(assigns) do
|
||||
~H"""
|
||||
<Layouts.app flash={@flash} current_user={@current_user}>
|
||||
<%!-- Header with Back button, Name, and Edit button --%>
|
||||
<div class="flex items-center justify-between gap-4 pb-4">
|
||||
<.button navigate={~p"/members"} aria-label={gettext("Back to members list")}>
|
||||
<.icon name="hero-arrow-left" class="size-4" />
|
||||
{gettext("Back")}
|
||||
</.button>
|
||||
|
||||
<h1 class="text-2xl font-bold text-center flex-1">
|
||||
{MvWeb.Helpers.MemberHelpers.display_name(@member)}
|
||||
</h1>
|
||||
|
||||
<%= if can?(@current_user, :update, @member) do %>
|
||||
<.header>
|
||||
{MvWeb.Helpers.MemberHelpers.display_name(@member)}
|
||||
<:actions>
|
||||
<.button
|
||||
variant="primary"
|
||||
navigate={~p"/members/#{@member}/edit?return_to=show"}
|
||||
data-testid="member-edit"
|
||||
navigate={~p"/members"}
|
||||
variant="neutral"
|
||||
aria-label={gettext("Back to members list")}
|
||||
>
|
||||
{gettext("Edit Member")}
|
||||
<.icon name="hero-arrow-left" class="size-4" />
|
||||
{gettext("Back")}
|
||||
</.button>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= if can?(@current_user, :update, @member) do %>
|
||||
<.button
|
||||
variant="primary"
|
||||
navigate={~p"/members/#{@member}/edit?return_to=show"}
|
||||
data-testid="member-edit"
|
||||
>
|
||||
{gettext("Edit member")}
|
||||
</.button>
|
||||
<% end %>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<%!-- Tab Navigation --%>
|
||||
<div role="tablist" class="tabs tabs-bordered mb-6">
|
||||
<button
|
||||
role="tab"
|
||||
class={[
|
||||
"tab",
|
||||
if(@active_tab == :contact, do: "tab-active", else: "!text-gray-800")
|
||||
]}
|
||||
aria-selected={@active_tab == :contact}
|
||||
phx-click="switch_tab"
|
||||
phx-value-tab="contact"
|
||||
>
|
||||
<.icon name="hero-identification" class="size-4 mr-2" />
|
||||
{gettext("Contact Data")}
|
||||
</button>
|
||||
<button
|
||||
role="tab"
|
||||
class={[
|
||||
"tab",
|
||||
if(@active_tab == :membership_fees, do: "tab-active", else: "!text-gray-800")
|
||||
]}
|
||||
aria-selected={@active_tab == :membership_fees}
|
||||
phx-click="switch_tab"
|
||||
phx-value-tab="membership_fees"
|
||||
>
|
||||
<.icon name="hero-credit-card" class="size-4 mr-2" />
|
||||
{gettext("Membership Fees")}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-6 space-y-6">
|
||||
<%!-- Tab Navigation --%>
|
||||
<div role="tablist" class="tabs tabs-bordered">
|
||||
<button
|
||||
role="tab"
|
||||
class={[
|
||||
"tab",
|
||||
if(@active_tab == :contact, do: "tab-active", else: "!text-gray-800")
|
||||
]}
|
||||
aria-selected={@active_tab == :contact}
|
||||
phx-click="switch_tab"
|
||||
phx-value-tab="contact"
|
||||
>
|
||||
<.icon name="hero-identification" class="size-4 mr-2" />
|
||||
{gettext("Contact Data")}
|
||||
</button>
|
||||
<button
|
||||
role="tab"
|
||||
class={[
|
||||
"tab",
|
||||
if(@active_tab == :membership_fees, do: "tab-active", else: "!text-gray-800")
|
||||
]}
|
||||
aria-selected={@active_tab == :membership_fees}
|
||||
phx-click="switch_tab"
|
||||
phx-value-tab="membership_fees"
|
||||
>
|
||||
<.icon name="hero-credit-card" class="size-4 mr-2" />
|
||||
{gettext("Membership Fees")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<%= if @active_tab == :contact do %>
|
||||
<%!-- Contact Data Tab Content --%>
|
||||
<%!-- Personal Data and Custom Fields Row --%>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<%!-- Personal Data Section --%>
|
||||
<div>
|
||||
<.section_box title={gettext("Personal Data")}>
|
||||
<div class="space-y-4">
|
||||
<%!-- Name Row --%>
|
||||
<div class="flex gap-6">
|
||||
<.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" />
|
||||
</div>
|
||||
<%= if @active_tab == :contact do %>
|
||||
<%!-- Contact Data Tab Content --%>
|
||||
<%!-- Personal Data and Custom Fields Row --%>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<%!-- Personal Data Section --%>
|
||||
<div>
|
||||
<.section_box title={gettext("Personal Data")}>
|
||||
<div class="space-y-4">
|
||||
<%!-- Name Row --%>
|
||||
<div class="flex gap-6">
|
||||
<.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" />
|
||||
</div>
|
||||
|
||||
<%!-- Address --%>
|
||||
<div>
|
||||
<.data_field label={gettext("Address")} value={format_address(@member)} />
|
||||
</div>
|
||||
<%!-- Address --%>
|
||||
<div>
|
||||
<.data_field label={gettext("Address")} value={format_address(@member)} />
|
||||
</div>
|
||||
|
||||
<%!-- Email --%>
|
||||
<div>
|
||||
<.data_field label={gettext("Email")}>
|
||||
<a
|
||||
href={"mailto:#{MvWeb.MemberLive.Index.format_member_email(@member)}"}
|
||||
class="text-blue-700 hover:text-blue-800 underline"
|
||||
>
|
||||
{@member.email}
|
||||
</a>
|
||||
</.data_field>
|
||||
</div>
|
||||
<%!-- Email --%>
|
||||
<div>
|
||||
<.data_field label={gettext("Email")}>
|
||||
<a
|
||||
href={"mailto:#{MvWeb.MemberLive.Index.format_member_email(@member)}"}
|
||||
class="text-blue-700 hover:text-blue-800 underline"
|
||||
>
|
||||
{@member.email}
|
||||
</a>
|
||||
</.data_field>
|
||||
</div>
|
||||
|
||||
<%!-- Membership Dates Row --%>
|
||||
<div class="flex gap-6">
|
||||
<.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"
|
||||
/>
|
||||
</div>
|
||||
<%!-- Membership Dates Row --%>
|
||||
<div class="flex gap-6">
|
||||
<.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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<%!-- Linked User: only show when current user can see other users (e.g. admin).
|
||||
<%!-- Linked User: only show when current user can see other users (e.g. admin).
|
||||
read_only cannot see linked user, so hide the section to avoid "No user linked" when
|
||||
a user is linked but not visible. --%>
|
||||
<%= if can_access_page?(@current_user, "/users") do %>
|
||||
<%= if can_access_page?(@current_user, "/users") do %>
|
||||
<div>
|
||||
<.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}
|
||||
</.link>
|
||||
<% else %>
|
||||
<span class="text-base-content/70 italic">{gettext("No user linked")}</span>
|
||||
<% end %>
|
||||
</.data_field>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%!-- Groups (in Personal Data) --%>
|
||||
<% groups = @member.groups || [] %>
|
||||
<div>
|
||||
<.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}
|
||||
</.link>
|
||||
<.data_field label={gettext("Groups")}>
|
||||
<%= if Enum.empty?(groups) do %>
|
||||
<span class="text-base-content/70 italic">{gettext("No groups")}</span>
|
||||
<% else %>
|
||||
<span class="text-base-content/70 italic">{gettext("No user linked")}</span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for group <- groups do %>
|
||||
<.button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
navigate={~p"/groups/#{group.slug}"}
|
||||
aria-label={gettext("Member of group %{name}", name: group.name)}
|
||||
>
|
||||
{group.name}
|
||||
</.button>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</.data_field>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%!-- Groups (in Personal Data) --%>
|
||||
<% groups = @member.groups || [] %>
|
||||
<div>
|
||||
<.data_field label={gettext("Groups")}>
|
||||
<%= if Enum.empty?(groups) do %>
|
||||
<span class="text-base-content/70 italic">{gettext("No groups")}</span>
|
||||
<% else %>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for group <- groups do %>
|
||||
<.link
|
||||
navigate={~p"/groups/#{group.slug}"}
|
||||
class="btn btn-xs btn-outline btn-primary"
|
||||
aria-label={gettext("Member of group %{name}", name: group.name)}
|
||||
>
|
||||
{group.name}
|
||||
</.link>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</.data_field>
|
||||
</div>
|
||||
|
||||
<%!-- Notes --%>
|
||||
<%= if @member.notes && String.trim(@member.notes) != "" do %>
|
||||
<div>
|
||||
<.data_field label={gettext("Notes")}>
|
||||
<p class="whitespace-pre-wrap text-base-content/80">{@member.notes}</p>
|
||||
</.data_field>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</.section_box>
|
||||
</div>
|
||||
|
||||
<%!-- Custom Fields Section --%>
|
||||
<%= if Enum.any?(@custom_fields) do %>
|
||||
<div>
|
||||
<.section_box title={gettext("Custom Fields")}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<%= for custom_field <- @custom_fields do %>
|
||||
<% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %>
|
||||
<.data_field label={custom_field.name}>
|
||||
{format_custom_field_value(cfv, custom_field.value_type)}
|
||||
</.data_field>
|
||||
<%!-- Notes --%>
|
||||
<%= if @member.notes && String.trim(@member.notes) != "" do %>
|
||||
<div>
|
||||
<.data_field label={gettext("Notes")}>
|
||||
<p class="whitespace-pre-wrap text-base-content/80">{@member.notes}</p>
|
||||
</.data_field>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</.section_box>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%!-- Payment Data Section --%>
|
||||
<div class="w-full">
|
||||
<.section_box title={gettext("Payment Data")}>
|
||||
<%= if @member.membership_fee_type do %>
|
||||
<div class="flex gap-6 flex-wrap">
|
||||
<.data_field
|
||||
label={gettext("Type")}
|
||||
value={@member.membership_fee_type.name}
|
||||
class="min-w-32"
|
||||
/>
|
||||
<.data_field
|
||||
label={gettext("Membership Fee")}
|
||||
value={MembershipFeeHelpers.format_currency(@member.membership_fee_type.amount)}
|
||||
class="min-w-24"
|
||||
/>
|
||||
<.data_field
|
||||
label={gettext("Payment Interval")}
|
||||
value={MembershipFeeHelpers.format_interval(@member.membership_fee_type.interval)}
|
||||
class="min-w-32"
|
||||
/>
|
||||
<.data_field label={gettext("Last Cycle")} class="min-w-32">
|
||||
<%= if @member.last_cycle_status do %>
|
||||
<% status = @member.last_cycle_status %>
|
||||
<span class={["badge", MembershipFeeHelpers.status_color(status)]}>
|
||||
{format_status_label(status)}
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="badge badge-ghost">{gettext("No cycles")}</span>
|
||||
<% end %>
|
||||
</.data_field>
|
||||
<.data_field label={gettext("Current Cycle")} class="min-w-36">
|
||||
<%= if @member.current_cycle_status do %>
|
||||
<% status = @member.current_cycle_status %>
|
||||
<span class={["badge", MembershipFeeHelpers.status_color(status)]}>
|
||||
{format_status_label(status)}
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="badge badge-ghost">{gettext("No cycles")}</span>
|
||||
<% end %>
|
||||
</.data_field>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-base-content/70 italic">
|
||||
{gettext("No membership fee type assigned")}
|
||||
<%!-- Custom Fields Section --%>
|
||||
<%= if Enum.any?(@custom_fields) do %>
|
||||
<div>
|
||||
<.section_box title={gettext("Custom Fields")}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<%= for custom_field <- @custom_fields do %>
|
||||
<% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %>
|
||||
<.data_field label={custom_field.name}>
|
||||
{format_custom_field_value(cfv, custom_field.value_type)}
|
||||
</.data_field>
|
||||
<% end %>
|
||||
</div>
|
||||
</.section_box>
|
||||
</div>
|
||||
<% end %>
|
||||
</.section_box>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= if @active_tab == :membership_fees do %>
|
||||
<%!-- Membership Fees Tab Content --%>
|
||||
<.live_component
|
||||
module={MvWeb.MemberLive.Show.MembershipFeesComponent}
|
||||
id={"membership-fees-#{@member.id}"}
|
||||
member={@member}
|
||||
current_user={@current_user}
|
||||
vereinfacht_receipts={@vereinfacht_receipts}
|
||||
/>
|
||||
<% end %>
|
||||
<%!-- Payment Data Section --%>
|
||||
<div class="w-full">
|
||||
<.section_box title={gettext("Payment Data")}>
|
||||
<%= if @member.membership_fee_type do %>
|
||||
<div class="flex gap-6 flex-wrap">
|
||||
<.data_field
|
||||
label={gettext("Type")}
|
||||
value={@member.membership_fee_type.name}
|
||||
class="min-w-32"
|
||||
/>
|
||||
<.data_field
|
||||
label={gettext("Membership Fee")}
|
||||
value={MembershipFeeHelpers.format_currency(@member.membership_fee_type.amount)}
|
||||
class="min-w-24"
|
||||
/>
|
||||
<.data_field
|
||||
label={gettext("Payment Interval")}
|
||||
value={MembershipFeeHelpers.format_interval(@member.membership_fee_type.interval)}
|
||||
class="min-w-32"
|
||||
/>
|
||||
<.data_field label={gettext("Last Cycle")} class="min-w-32">
|
||||
<%= if @member.last_cycle_status do %>
|
||||
<% status = @member.last_cycle_status %>
|
||||
<span class={["badge", MembershipFeeHelpers.status_color(status)]}>
|
||||
{format_status_label(status)}
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="badge badge-ghost">{gettext("No cycles")}</span>
|
||||
<% end %>
|
||||
</.data_field>
|
||||
<.data_field label={gettext("Current Cycle")} class="min-w-36">
|
||||
<%= if @member.current_cycle_status do %>
|
||||
<% status = @member.current_cycle_status %>
|
||||
<span class={["badge", MembershipFeeHelpers.status_color(status)]}>
|
||||
{format_status_label(status)}
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="badge badge-ghost">{gettext("No cycles")}</span>
|
||||
<% end %>
|
||||
</.data_field>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-base-content/70 italic">
|
||||
{gettext("No membership fee type assigned")}
|
||||
</div>
|
||||
<% end %>
|
||||
</.section_box>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if @active_tab == :membership_fees do %>
|
||||
<%!-- Membership Fees Tab Content --%>
|
||||
<.live_component
|
||||
module={MvWeb.MemberLive.Show.MembershipFeesComponent}
|
||||
id={"membership-fees-#{@member.id}"}
|
||||
member={@member}
|
||||
current_user={@current_user}
|
||||
vereinfacht_receipts={@vereinfacht_receipts}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
</Layouts.app>
|
||||
"""
|
||||
end
|
||||
|
|
@ -403,7 +411,7 @@ defmodule MvWeb.MemberLive.Show do
|
|||
~H"""
|
||||
<a
|
||||
href={"mailto:#{@email}"}
|
||||
class="text-blue-700 hover:text-blue-800 underline"
|
||||
class="link link-primary"
|
||||
>
|
||||
{@display}
|
||||
</a>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue