Packages

phoenix_kit

1.7.76
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 emails web metrics.html.heex
Raw

lib/modules/emails/web/metrics.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={gettext("Email Analytics")}
current_path={@url_path}
project_title={@project_title}
>
<div class="container flex-col mx-auto px-4 py-6">
<.admin_page_header
back={Routes.path("/admin")}
title={gettext("Email Dashboard")}
subtitle={gettext("Comprehensive email performance metrics and insights")}
/>
<%!-- Controls Section --%>
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between mb-6 gap-4">
<%!-- Period Selection --%>
<div class="flex flex-wrap gap-2">
<button
class={[
"btn btn-sm",
(@period == :last_24_hours && "btn-primary") || "btn-outline"
]}
phx-click="change_period"
phx-value-period="last_24_hours"
>
{gettext("24 Hours")}
</button>
<button
class={[
"btn btn-sm",
(@period == :last_7_days && "btn-primary") || "btn-outline"
]}
phx-click="change_period"
phx-value-period="last_7_days"
>
{gettext("7 Days")}
</button>
<button
class={[
"btn btn-sm",
(@period == :last_30_days && "btn-primary") || "btn-outline"
]}
phx-click="change_period"
phx-value-period="last_30_days"
>
{gettext("30 Days")}
</button>
<button
class={[
"btn btn-sm",
(@period == :last_90_days && "btn-primary") || "btn-outline"
]}
phx-click="change_period"
phx-value-period="last_90_days"
>
{gettext("90 Days")}
</button>
<button
class="btn btn-sm btn-outline"
phx-click="toggle_custom_range"
>
{gettext("Custom Range")}
</button>
</div>
<%!-- Action Buttons --%>
<div class="flex gap-2">
<button
class="btn btn-sm btn-outline"
phx-click="refresh"
disabled={@loading}
>
<%= if @loading do %>
<span class="loading loading-spinner loading-xs"></span>
<% else %>
<PhoenixKitWeb.Components.Core.Icons.icon_refresh class="w-4 h-4" />
<% end %>
{gettext("Refresh")}
</button>
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-sm btn-outline">
<PhoenixKitWeb.Components.Core.Icons.icon_download class="w-4 h-4" /> {gettext(
"Export"
)}
</div>
<ul
tabindex="0"
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52"
>
<li>
<a phx-click="export_metrics" phx-value-format="csv">
{gettext("Export as CSV")}
</a>
</li>
<li>
<a phx-click="export_metrics" phx-value-format="json">
{gettext("Export as JSON")}
</a>
</li>
</ul>
</div>
</div>
</div>
<%!-- Custom Date Range Modal --%>
<%= if @custom_range do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">{gettext("Select Custom Date Range")}</h3>
<form phx-submit="apply_custom_range">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="label">
<span class="label-text">{gettext("Start Date")}</span>
</label>
<input
type="date"
name="start_date"
class="input input-bordered w-full"
max={Date.utc_today() |> Date.to_iso8601()}
required
/>
</div>
<div>
<label class="label">
<span class="label-text">{gettext("End Date")}</span>
</label>
<input
type="date"
name="end_date"
class="input input-bordered w-full"
max={Date.utc_today() |> Date.to_iso8601()}
required
/>
</div>
</div>
<div class="modal-action">
<button type="submit" class="btn btn-primary">{gettext("Apply")}</button>
<button type="button" class="btn" phx-click="toggle_custom_range">
{gettext("Cancel")}
</button>
</div>
</form>
</div>
</div>
<% end %>
<%!-- Loading State --%>
<%= if @loading do %>
<div class="flex justify-center items-center h-32">
<span class="loading loading-spinner loading-lg"></span>
<span class="ml-3 text-lg">{gettext("Loading analytics data...")}</span>
</div>
<% else %>
<%!-- KPI Cards --%>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<%!-- Total Sent --%>
<div class="card bg-primary text-primary-content shadow-sm">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<p class="text-primary-content/70 text-sm">{gettext("Total Sent")}</p>
<p class="text-2xl font-bold">{UtilsNumber.format(@metrics.total_sent || 0)}</p>
</div>
<div class="text-primary-content/70">
<PhoenixKitWeb.Components.Core.Icons.icon_email class="w-8 h-8" />
</div>
</div>
</div>
</div>
<%!-- Delivery Rate --%>
<div class="card bg-success text-success-content shadow-sm">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<p class="text-success-content/70 text-sm">{gettext("Delivery Rate")}</p>
<p class="text-2xl font-bold">
{UtilsNumber.format_percentage(@metrics.delivery_rate || 0)}
</p>
</div>
<div class="text-success-content/70">
<PhoenixKitWeb.Components.Core.Icons.icon_check class="w-8 h-8" />
</div>
</div>
</div>
</div>
<%!-- Bounce Rate --%>
<div class="card bg-warning text-warning-content shadow-sm">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<p class="text-warning-content/70 text-sm">{gettext("Bounce Rate")}</p>
<p class="text-2xl font-bold">
{UtilsNumber.format_percentage(@metrics.bounce_rate || 0)}
</p>
</div>
<div class="text-warning-content/70">
<PhoenixKitWeb.Components.Core.Icons.icon_warning_filled class="w-8 h-8" />
</div>
</div>
</div>
</div>
<%!-- Open Rate --%>
<div class="card bg-info text-info-content shadow-sm">
<div class="card-body p-4">
<div class="flex items-center justify-between">
<div>
<p class="text-info-content/70 text-sm">{gettext("Open Rate")}</p>
<p class="text-2xl font-bold">
{UtilsNumber.format_percentage(@metrics.open_rate || 0)}
</p>
</div>
<div class="text-info-content/70">
<PhoenixKitWeb.Components.Core.Icons.icon_eye class="w-8 h-8" />
</div>
</div>
</div>
</div>
</div>
<%!-- Charts Section --%>
<div
class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"
id="email-charts-container"
phx-hook="EmailCharts"
>
<%!-- Delivery Trends Chart --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-lg mb-4">{gettext("Delivery Trends")}</h2>
<div
class="w-full h-64"
id="delivery-chart-container"
data-chart-data={Jason.encode!(Map.get(@charts_data, :delivery_trend, %{}))}
>
<canvas id="delivery-trend-chart" width="400" height="200" phx-update="ignore">
</canvas>
</div>
</div>
</div>
<%!-- Engagement Chart --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-lg mb-4">{gettext("Engagement Metrics")}</h2>
<div
class="w-full h-64"
id="engagement-chart-container"
data-chart-data={Jason.encode!(Map.get(@charts_data, :engagement, %{}))}
>
<canvas id="engagement-chart" width="400" height="200" phx-update="ignore"></canvas>
</div>
</div>
</div>
</div>
<%!-- Additional Analytics --%>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<%!-- Provider Performance --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-lg mb-4">{gettext("Provider Performance")}</h2>
<%= if @metrics.by_provider && length(Map.keys(@metrics.by_provider)) > 0 do %>
<div class="overflow-x-auto">
<.table_default size="sm" class="w-full">
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>
{gettext("Provider")}
</.table_default_header_cell>
<.table_default_header_cell>{gettext("Sent")}</.table_default_header_cell>
<.table_default_header_cell>
{gettext("Delivery Rate")}
</.table_default_header_cell>
<.table_default_header_cell>
{gettext("Bounce Rate")}
</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<%= for {provider, stats} <- @metrics.by_provider do %>
<.table_default_row>
<.table_default_cell class="font-medium">{provider}</.table_default_cell>
<.table_default_cell>
{UtilsNumber.format(stats.total_sent || 0)}
</.table_default_cell>
<.table_default_cell>
<span class={[
"badge badge-sm h-auto",
((stats.delivery_rate || 0) >= 95 && "badge-success") ||
((stats.delivery_rate || 0) >= 85 && "badge-warning") ||
"badge-error"
]}>
{UtilsNumber.format_percentage(stats.delivery_rate || 0)}
</span>
</.table_default_cell>
<.table_default_cell>
{UtilsNumber.format_percentage(stats.bounce_rate || 0)}
</.table_default_cell>
</.table_default_row>
<% end %>
</.table_default_body>
</.table_default>
</div>
<% else %>
<div class="text-center text-base-content/50 py-8">
{gettext("No provider data available")}
</div>
<% end %>
</div>
</div>
<%!-- Recent Activity --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-lg mb-4">{gettext("System Status")}</h2>
<div class="space-y-3">
<div class="flex items-center justify-between">
<span class="text-sm">{gettext("Email")}</span>
<span class="badge badge-success">{gettext("Active")}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm">{gettext("Last Updated")}</span>
<span class="text-sm text-base-content/70">
{UtilsDate.format_datetime_with_user_format(@last_updated)}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm">{gettext("Total Emails Today")}</span>
<span class="text-sm font-medium">
{UtilsNumber.format(@metrics.today_count || 0)}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm">{gettext("Data Retention")}</span>
<span class="text-sm text-base-content/70">
{gettext("%{days} days", days: PhoenixKit.Modules.Emails.get_retention_days())}
</span>
</div>
</div>
</div>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>