Packages

phoenix_kit

1.7.49
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules tickets web list.ex
Raw

lib/modules/tickets/web/list.ex

defmodule PhoenixKit.Modules.Tickets.Web.List do
@moduledoc """
LiveView for displaying and managing support tickets in PhoenixKit admin panel.
Provides comprehensive ticket management interface with filtering, searching,
and quick actions for the support ticketing system.
## Features
- **Real-time Ticket List**: Live updates of tickets
- **Status Filtering**: By open, in_progress, resolved, closed
- **Assignment Filtering**: By handler, unassigned
- **Search Functionality**: Search across titles and descriptions
- **Pagination**: Handle large volumes of tickets
- **Quick Actions**: View, assign, change status
- **Statistics Summary**: Key metrics (open, in_progress, resolved, closed)
## Route
This LiveView is mounted at `{prefix}/admin/tickets` and requires
appropriate admin or SupportAgent permissions.
## Permissions
Access is restricted to users with admin, owner, or SupportAgent roles.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Modules.Tickets
alias PhoenixKit.Modules.Tickets.Events
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Routes
@impl true
def mount(_params, _session, socket) do
if tickets_enabled?() do
current_user = socket.assigns[:phoenix_kit_current_user]
# Subscribe to ticket events for real-time updates
Events.subscribe_to_all()
project_title = Settings.get_project_title()
socket =
socket
|> assign(:page_title, "Support Tickets")
|> assign(:project_title, project_title)
|> assign(:current_user, current_user)
|> assign(:tickets, [])
|> assign(:total_count, 0)
|> assign(:stats, Tickets.get_stats())
|> assign(:loading, true)
|> assign_filter_defaults()
|> assign_pagination_defaults()
{:ok, socket}
else
{:ok,
socket
|> put_flash(:error, "Tickets module is not enabled")
|> push_navigate(to: Routes.path("/admin"))}
end
end
@impl true
def handle_params(params, uri, socket) do
socket =
socket
|> assign(:url_path, URI.parse(uri).path)
|> apply_params(params)
|> load_tickets()
{:noreply, assign(socket, :loading, false)}
end
@impl true
def handle_event("filter", params, socket) do
filter_params = %{}
filter_params =
case Map.get(params, "search") do
%{"query" => query} -> Map.put(filter_params, "search", String.trim(query || ""))
_ -> filter_params
end
filter_params =
case Map.get(params, "filters") do
%{"status" => status} when status != "" ->
Map.put(filter_params, "status", status)
_ ->
filter_params
end
filter_params =
case Map.get(params, "filters") do
%{"assigned_to" => assigned} when assigned != "" ->
Map.put(filter_params, "assigned_to", assigned)
_ ->
filter_params
end
{:noreply,
push_patch(socket, to: Routes.path("/admin/tickets", map_to_keyword(filter_params)))}
end
@impl true
def handle_event("clear_filters", _params, socket) do
{:noreply, push_patch(socket, to: Routes.path("/admin/tickets"))}
end
@impl true
def handle_event("change_page", %{"page" => page}, socket) do
page = String.to_integer(page)
current_params = build_current_params(socket)
params = Map.put(current_params, "page", page)
{:noreply, push_patch(socket, to: Routes.path("/admin/tickets", map_to_keyword(params)))}
end
@impl true
def handle_info({:ticket_created, ticket}, socket) do
# Prepend new ticket to list if it matches current filters
socket =
if ticket_matches_filters?(ticket, socket) do
tickets = [ticket | socket.assigns.tickets]
total_count = socket.assigns.total_count + 1
socket
|> assign(:tickets, tickets)
|> assign(:total_count, total_count)
|> assign(:stats, Tickets.get_stats())
else
# Just update stats if ticket doesn't match current filters
assign(socket, :stats, Tickets.get_stats())
end
{:noreply, socket}
end
@impl true
def handle_info({:ticket_updated, ticket}, socket) do
# Update ticket in list if present
tickets =
Enum.map(socket.assigns.tickets, fn t ->
if t.uuid == ticket.uuid, do: ticket, else: t
end)
{:noreply, assign(socket, :tickets, tickets)}
end
@impl true
def handle_info({:ticket_status_changed, ticket, _old_status, _new_status}, socket) do
# Update ticket and stats
tickets =
Enum.map(socket.assigns.tickets, fn t ->
if t.uuid == ticket.uuid, do: ticket, else: t
end)
socket =
socket
|> assign(:tickets, tickets)
|> assign(:stats, Tickets.get_stats())
{:noreply, socket}
end
@impl true
def handle_info({:ticket_assigned, ticket, _old_assignee, _new_assignee}, socket) do
# Update ticket in list
tickets =
Enum.map(socket.assigns.tickets, fn t ->
if t.uuid == ticket.uuid, do: ticket, else: t
end)
{:noreply, assign(socket, :tickets, tickets)}
end
@impl true
def handle_info({:tickets_bulk_updated, _tickets, _changes}, socket) do
# Reload tickets and stats on bulk update
socket =
socket
|> load_tickets()
|> assign(:stats, Tickets.get_stats())
{:noreply, socket}
end
# Private functions
defp tickets_enabled? do
Tickets.enabled?()
end
defp assign_filter_defaults(socket) do
socket
|> assign(:status_filter, nil)
|> assign(:assigned_to_filter, nil)
|> assign(:search_query, nil)
end
defp assign_pagination_defaults(socket) do
per_page = Settings.get_setting("tickets_per_page", "20") |> String.to_integer()
socket
|> assign(:page, 1)
|> assign(:per_page, per_page)
|> assign(:total_pages, 1)
end
defp apply_params(socket, params) do
page = params |> Map.get("page", "1") |> String.to_integer() |> max(1)
status = Map.get(params, "status")
assigned_to = Map.get(params, "assigned_to")
search = Map.get(params, "search")
socket
|> assign(:page, page)
|> assign(:status_filter, status)
|> assign(:assigned_to_filter, assigned_to)
|> assign(:search_query, search)
end
defp load_tickets(socket) do
opts = build_query_opts(socket)
tickets = Tickets.list_tickets(opts)
total_count = count_filtered_tickets(socket)
total_pages = max(1, ceil(total_count / socket.assigns.per_page))
socket
|> assign(:tickets, tickets)
|> assign(:total_count, total_count)
|> assign(:total_pages, total_pages)
end
defp build_query_opts(socket) do
opts = [
page: socket.assigns.page,
per_page: socket.assigns.per_page,
preload: [:user, :assigned_to]
]
opts =
case socket.assigns.status_filter do
nil -> opts
status -> Keyword.put(opts, :status, status)
end
opts =
case socket.assigns.assigned_to_filter do
nil ->
opts
"unassigned" ->
Keyword.put(opts, :assigned_to_id, nil)
handler_id ->
Keyword.put(opts, :assigned_to_id, handler_id)
end
case socket.assigns.search_query do
nil -> opts
"" -> opts
search -> Keyword.put(opts, :search, search)
end
end
defp count_filtered_tickets(socket) do
# For simplicity, count based on status filter only
case socket.assigns.status_filter do
nil -> Tickets.get_stats().total
status -> Map.get(Tickets.get_stats(), String.to_atom(status), 0)
end
end
defp build_current_params(socket) do
params = %{}
params =
if socket.assigns.status_filter,
do: Map.put(params, "status", socket.assigns.status_filter),
else: params
params =
if socket.assigns.assigned_to_filter,
do: Map.put(params, "assigned_to", socket.assigns.assigned_to_filter),
else: params
if socket.assigns.search_query,
do: Map.put(params, "search", socket.assigns.search_query),
else: params
end
defp map_to_keyword(map) when is_map(map) do
Enum.map(map, fn {k, v} -> {String.to_existing_atom(k), v} end)
end
defp ticket_matches_filters?(ticket, socket) do
status_filter = socket.assigns.status_filter
assigned_to_filter = socket.assigns.assigned_to_filter
search_query = socket.assigns.search_query
matches_status?(ticket, status_filter) and
matches_assigned?(ticket, assigned_to_filter) and
matches_search?(ticket, search_query)
end
defp matches_status?(_ticket, nil), do: true
defp matches_status?(ticket, status), do: ticket.status == status
defp matches_assigned?(_ticket, nil), do: true
defp matches_assigned?(ticket, "unassigned"), do: is_nil(ticket.assigned_to_id)
defp matches_assigned?(ticket, handler_id),
do: to_string(ticket.assigned_to_id) == handler_id
defp matches_search?(_ticket, nil), do: true
defp matches_search?(_ticket, ""), do: true
defp matches_search?(ticket, query) do
query = String.downcase(query)
String.contains?(String.downcase(ticket.title || ""), query) or
String.contains?(String.downcase(ticket.description || ""), query)
end
end