feat: add bulk email copy for selected members (#230)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Copy selected members' emails to clipboard in 'First Last <email>' format
This commit is contained in:
parent
e803dbdf8b
commit
e2ace3d2a8
11 changed files with 661 additions and 61 deletions
|
|
@ -18,6 +18,7 @@ defmodule MvWeb.MemberLive.Index do
|
|||
- `delete` - Remove a member from the database
|
||||
- `select_member` - Toggle individual member selection
|
||||
- `select_all` - Toggle selection of all visible members
|
||||
- `copy_emails` - Copy email addresses of selected members to clipboard
|
||||
|
||||
## Implementation Notes
|
||||
- Search uses PostgreSQL full-text search (plainto_tsquery)
|
||||
|
|
@ -116,6 +117,49 @@ defmodule MvWeb.MemberLive.Index do
|
|||
{:noreply, assign(socket, :selected_members, selected)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("copy_emails", _params, socket) do
|
||||
selected_ids = socket.assigns.selected_members
|
||||
|
||||
if selected_ids == [] do
|
||||
{:noreply, put_flash(socket, :error, gettext("No members selected"))}
|
||||
else
|
||||
# Filter members that are in the selection
|
||||
selected_members =
|
||||
socket.assigns.members
|
||||
|> Enum.filter(fn member -> member.id in selected_ids end)
|
||||
|
||||
# Format emails and filter out members without email
|
||||
formatted_emails =
|
||||
selected_members
|
||||
|> Enum.filter(fn member -> member.email && member.email != "" end)
|
||||
|> Enum.map(&format_member_email/1)
|
||||
|
||||
email_count = length(formatted_emails)
|
||||
|
||||
if email_count == 0 do
|
||||
{:noreply, put_flash(socket, :error, gettext("No email addresses found"))}
|
||||
else
|
||||
email_string = Enum.join(formatted_emails, "; ")
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> push_event("copy_to_clipboard", %{text: email_string})
|
||||
|> put_flash(
|
||||
:info,
|
||||
ngettext(
|
||||
"Copied %{count} email address to clipboard",
|
||||
"Copied %{count} email addresses to clipboard",
|
||||
email_count,
|
||||
count: email_count
|
||||
)
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Handle Infos from Child Components
|
||||
# -----------------------------------------------------------------
|
||||
|
|
@ -733,4 +777,22 @@ defmodule MvWeb.MemberLive.Index do
|
|||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a member's email in the format "First Last <email>"
|
||||
# Used for copy_emails feature to create email-client-friendly format.
|
||||
defp format_member_email(member) do
|
||||
first_name = member.first_name || ""
|
||||
last_name = member.last_name || ""
|
||||
|
||||
name =
|
||||
[first_name, last_name]
|
||||
|> Enum.filter(&(&1 != ""))
|
||||
|> Enum.join(" ")
|
||||
|
||||
if name == "" do
|
||||
member.email
|
||||
else
|
||||
"#{name} <#{member.email}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@
|
|||
<.header>
|
||||
{gettext("Members")}
|
||||
<:actions>
|
||||
<.button
|
||||
:if={Enum.any?(@members, &(&1.id in @selected_members))}
|
||||
id="copy-emails-btn"
|
||||
phx-hook="CopyToClipboard"
|
||||
phx-click="copy_emails"
|
||||
aria-label={gettext("Copy email addresses of selected members")}
|
||||
>
|
||||
<.icon name="hero-clipboard-document" />
|
||||
{gettext("Copy emails")} ({Enum.count(@members, &(&1.id in @selected_members))})
|
||||
</.button>
|
||||
<.button variant="primary" navigate={~p"/members/new"}>
|
||||
<.icon name="hero-plus" /> {gettext("New Member")}
|
||||
</.button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue