Remove paid field from Member resource, database migration, tests, seeds, and UI. This field is no longer needed as payment status is now tracked via membership fee cycles.
361 lines
9.8 KiB
Text
361 lines
9.8 KiB
Text
<Layouts.app flash={@flash} current_user={@current_user}>
|
|
<.header>
|
|
{gettext("Members")}
|
|
<:actions>
|
|
<.button
|
|
class="secondary"
|
|
id="copy-emails-btn"
|
|
phx-hook="CopyToClipboard"
|
|
phx-click="copy_emails"
|
|
disabled={not @any_selected?}
|
|
aria-label={gettext("Copy email addresses of selected members")}
|
|
>
|
|
<.icon name="hero-clipboard-document" />
|
|
{gettext("Copy email addresses")} ({@selected_count})
|
|
</.button>
|
|
<.button
|
|
class="secondary"
|
|
id="open-email-btn"
|
|
href={"mailto:?bcc=" <> @mailto_bcc}
|
|
disabled={not @any_selected?}
|
|
aria-label={gettext("Open email program with BCC recipients")}
|
|
>
|
|
<.icon name="hero-envelope" />
|
|
{gettext("Open in email program")}
|
|
</.button>
|
|
<.button variant="primary" navigate={~p"/members/new"}>
|
|
<.icon name="hero-plus" /> {gettext("New Member")}
|
|
</.button>
|
|
</:actions>
|
|
</.header>
|
|
|
|
<div class="flex flex-wrap gap-4 items-center">
|
|
<.live_component
|
|
module={MvWeb.Components.SearchBarComponent}
|
|
id="search-bar"
|
|
query={@query}
|
|
placeholder={gettext("Search...")}
|
|
/>
|
|
<.live_component
|
|
module={MvWeb.Components.PaymentFilterComponent}
|
|
id="payment-filter"
|
|
paid_filter={@paid_filter}
|
|
member_count={length(@members)}
|
|
/>
|
|
<div class="flex gap-2 items-center">
|
|
<button
|
|
type="button"
|
|
phx-click="toggle_cycle_view"
|
|
class={[
|
|
"btn btn-sm",
|
|
if(@show_current_cycle, do: "btn-primary", else: "btn-outline")
|
|
]}
|
|
aria-label={
|
|
if(@show_current_cycle,
|
|
do: gettext("Show last completed cycle"),
|
|
else: gettext("Show current cycle")
|
|
)
|
|
}
|
|
>
|
|
<.icon name="hero-arrow-path" class="size-4" />
|
|
{if(@show_current_cycle, do: gettext("Current Cycle"), else: gettext("Last Cycle"))}
|
|
</button>
|
|
<div class="dropdown">
|
|
<label tabindex="0" class="btn btn-sm btn-outline">
|
|
<.icon name="hero-funnel" class="size-4" />
|
|
{gettext("Membership Fee")}
|
|
</label>
|
|
<ul
|
|
tabindex="0"
|
|
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"
|
|
>
|
|
<li>
|
|
<button
|
|
type="button"
|
|
phx-click="filter_unpaid_cycles"
|
|
phx-value-filter="unpaid_last"
|
|
class={if(@membership_fee_status_filter == :unpaid_last, do: "active", else: "")}
|
|
>
|
|
{gettext("Unpaid in last cycle")}
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button
|
|
type="button"
|
|
phx-click="filter_unpaid_cycles"
|
|
phx-value-filter="unpaid_current"
|
|
class={if(@membership_fee_status_filter == :unpaid_current, do: "active", else: "")}
|
|
>
|
|
{gettext("Unpaid in current cycle")}
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button
|
|
type="button"
|
|
phx-click="filter_unpaid_cycles"
|
|
phx-value-filter=""
|
|
class={if(@membership_fee_status_filter == nil, do: "active", else: "")}
|
|
>
|
|
{gettext("All")}
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<.live_component
|
|
module={MvWeb.Components.FieldVisibilityDropdownComponent}
|
|
id="field-visibility-dropdown"
|
|
all_fields={@all_available_fields}
|
|
custom_fields={@all_custom_fields}
|
|
selected_fields={@user_field_selection}
|
|
/>
|
|
</div>
|
|
|
|
<.table
|
|
id="members"
|
|
rows={@members}
|
|
row_click={fn member -> JS.navigate(~p"/members/#{member}") end}
|
|
dynamic_cols={@dynamic_cols}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
>
|
|
|
|
<!-- <:col :let={member} label="Id">{member.id}</:col> -->
|
|
<:col
|
|
:let={member}
|
|
col_click={&MvWeb.MemberLive.Index.checkbox_column_click/1}
|
|
label={
|
|
~H"""
|
|
<.input
|
|
type="checkbox"
|
|
name="select_all"
|
|
phx-click="select_all"
|
|
checked={MapSet.equal?(@selected_members, @members |> Enum.map(& &1.id) |> MapSet.new())}
|
|
aria-label={gettext("Select all members")}
|
|
role="checkbox"
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
<.input
|
|
type="checkbox"
|
|
name={member.id}
|
|
checked={MapSet.member?(@selected_members, member.id)}
|
|
aria-label={gettext("Select member")}
|
|
role="checkbox"
|
|
/>
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:first_name in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_first_name}
|
|
field={:first_name}
|
|
label={gettext("First name")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.first_name}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:last_name in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_last_name}
|
|
field={:last_name}
|
|
label={gettext("Last name")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.last_name}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:email in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_email}
|
|
field={:email}
|
|
label={gettext("Email")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.email}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:street in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_street}
|
|
field={:street}
|
|
label={gettext("Street")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.street}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:house_number in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_house_number}
|
|
field={:house_number}
|
|
label={gettext("House Number")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.house_number}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:postal_code in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_postal_code}
|
|
field={:postal_code}
|
|
label={gettext("Postal Code")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.postal_code}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:city in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_city}
|
|
field={:city}
|
|
label={gettext("City")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.city}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:phone_number in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_phone_number}
|
|
field={:phone_number}
|
|
label={gettext("Phone Number")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{member.phone_number}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
:if={:join_date in @member_fields_visible}
|
|
label={
|
|
~H"""
|
|
<.live_component
|
|
module={MvWeb.Components.SortHeaderComponent}
|
|
id={:sort_join_date}
|
|
field={:join_date}
|
|
label={gettext("Join Date")}
|
|
sort_field={@sort_field}
|
|
sort_order={@sort_order}
|
|
/>
|
|
"""
|
|
}
|
|
>
|
|
{MvWeb.MemberLive.Index.format_date(member.join_date)}
|
|
</:col>
|
|
<:col
|
|
:let={member}
|
|
label={
|
|
~H"""
|
|
<div class="flex items-center gap-2">
|
|
<span>{gettext("Membership Fee Status")}</span>
|
|
<button
|
|
type="button"
|
|
phx-click="toggle_cycle_view"
|
|
class="btn btn-xs btn-ghost"
|
|
title={
|
|
if(@show_current_cycle,
|
|
do: gettext("Switch to last completed cycle"),
|
|
else: gettext("Switch to current cycle")
|
|
)
|
|
}
|
|
>
|
|
<.icon name="hero-arrow-path" class="size-3" />
|
|
</button>
|
|
</div>
|
|
"""
|
|
}
|
|
>
|
|
<%= if badge = MvWeb.MemberLive.Index.MembershipFeeStatus.format_cycle_status_badge(
|
|
MvWeb.MemberLive.Index.MembershipFeeStatus.get_cycle_status_for_member(member, @show_current_cycle)
|
|
) do %>
|
|
<span class={["badge", badge.color]}>
|
|
<.icon name={badge.icon} class="size-4" />
|
|
{badge.label}
|
|
</span>
|
|
<% else %>
|
|
<span class="badge badge-ghost">{gettext("No cycle")}</span>
|
|
<% end %>
|
|
</:col>
|
|
<:action :let={member}>
|
|
<div class="sr-only">
|
|
<.link navigate={~p"/members/#{member}"}>{gettext("Show")}</.link>
|
|
</div>
|
|
|
|
<.link navigate={~p"/members/#{member}/edit"}>{gettext("Edit")}</.link>
|
|
</:action>
|
|
|
|
<:action :let={member}>
|
|
<.link
|
|
phx-click={JS.push("delete", value: %{id: member.id}) |> hide("#row-#{member.id}")}
|
|
data-confirm={gettext("Are you sure?")}
|
|
>
|
|
{gettext("Delete")}
|
|
</.link>
|
|
</:action>
|
|
</.table>
|
|
</Layouts.app>
|