cover/Elixir.DaProductAppWeb.AnalyticsLive.html

1
:-(
defmodule DaProductAppWeb.AnalyticsLive do
2 @moduledoc """
3 LiveView for Analytics Dashboard with comprehensive insights.
4
5 Features:
6 - Transaction analytics and metrics
7 - QR validation analytics
8 - Performance metrics
9 - Daily trends and charts
10 - Corridor performance analysis
11 - Real-time data updates
12 """
13 use DaProductAppWeb, :live_view
14
15 alias DaProductApp.Accounts
16 alias DaProductApp.Monitoring
17
18 @impl true
19
:-(
def mount(_params, session, socket) do
20
:-(
case get_current_user(session) do
21
:-(
nil ->
22 {:ok, redirect(socket, to: ~p"/login")}
23
24 user ->
25
:-(
if has_access?(user) do
26
:-(
if connected?(socket) do
27 # Subscribe to analytics events for real-time updates
28
:-(
Phoenix.PubSub.subscribe(DaProductApp.PubSub, "analytics")
29
:-(
Phoenix.PubSub.subscribe(DaProductApp.PubSub, "transactions")
30
:-(
Phoenix.PubSub.subscribe(DaProductApp.PubSub, "qr_validations")
31 end
32
33 # Default to last 30 days
34
:-(
end_date = Date.utc_today()
35
:-(
start_date = Date.add(end_date, -30)
36
37
:-(
socket =
38 socket
39 |> assign(:current_user, user)
40 |> assign(:page_title, "Analytics Dashboard")
41 |> assign(:current_page, :analytics)
42 |> assign(:date_from, start_date)
43 |> assign(:date_to, end_date)
44 |> assign(:analytics_data, %{})
45 |> assign(:selected_period, "30_days")
46 |> load_analytics()
47
48 {:ok, socket}
49 else
50 {:ok,
51 socket
52 |> put_flash(:error, "Access denied")
53 |> redirect(to: ~p"/login")}
54 end
55 end
56 end
57
58 @impl true
59
:-(
def handle_params(params, _url, socket) do
60
:-(
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
61 end
62
63 defp apply_action(socket, :index, _params) do
64 socket
65
:-(
|> assign(:page_title, "Analytics Dashboard")
66 end
67
68 @impl true
69
:-(
def handle_event("change_period", %{"period" => period}, socket) do
70
:-(
{start_date, end_date} = get_date_range_for_period(period)
71
72
:-(
socket =
73 socket
74 |> assign(:selected_period, period)
75 |> assign(:date_from, start_date)
76 |> assign(:date_to, end_date)
77 |> load_analytics()
78
79 {:noreply, socket}
80 end
81
82
:-(
def handle_event("custom_date_range", %{"date_from" => date_from, "date_to" => date_to}, socket) do
83
:-(
parsed_date_from = if date_from != "", do: Date.from_iso8601!(date_from), else: Date.utc_today()
84
:-(
parsed_date_to = if date_to != "", do: Date.from_iso8601!(date_to), else: Date.utc_today()
85
86
:-(
socket =
87 socket
88 |> assign(:selected_period, "custom")
89 |> assign(:date_from, parsed_date_from)
90 |> assign(:date_to, parsed_date_to)
91 |> load_analytics()
92
93 {:noreply, socket}
94 end
95
96
:-(
def handle_event("refresh", _params, socket) do
97
:-(
socket =
98 socket
99 |> load_analytics()
100 |> put_flash(:info, "Analytics data refreshed")
101
102 {:noreply, socket}
103 end
104
105 # Handle real-time updates from PubSub
106 @impl true
107
:-(
def handle_info({:transaction_created, _transaction}, socket) do
108 {:noreply, load_analytics(socket)}
109 end
110
111
:-(
def handle_info({:transaction_updated, _transaction}, socket) do
112 {:noreply, load_analytics(socket)}
113 end
114
115
:-(
def handle_info({:qr_validation_created, _qr_validation}, socket) do
116 {:noreply, load_analytics(socket)}
117 end
118
119
:-(
def handle_info({:qr_validation_updated, _qr_validation}, socket) do
120 {:noreply, load_analytics(socket)}
121 end
122
123 defp load_analytics(socket) do
124
:-(
analytics_data = Monitoring.get_dashboard_analytics(socket.assigns.date_from, socket.assigns.date_to)
125
:-(
assign(socket, :analytics_data, analytics_data)
126 end
127
128
:-(
defp get_date_range_for_period(period) do
129
:-(
end_date = Date.utc_today()
130
131
:-(
start_date = case period do
132
:-(
"7_days" -> Date.add(end_date, -7)
133
:-(
"30_days" -> Date.add(end_date, -30)
134
:-(
"90_days" -> Date.add(end_date, -90)
135
:-(
"1_year" -> Date.add(end_date, -365)
136
:-(
_ -> Date.add(end_date, -30)
137 end
138
139 {start_date, end_date}
140 end
141
142 # Helper functions for the template
143
:-(
defp format_amount(nil), do: "0"
144 defp format_amount(amount) when is_binary(amount) do
145
:-(
case Decimal.parse(amount) do
146
:-(
{decimal, _} -> format_decimal(decimal)
147
:-(
:error -> amount
148 end
149 end
150
:-(
defp format_amount(%Decimal{} = amount), do: format_decimal(amount)
151
:-(
defp format_amount(amount) when is_number(amount), do: :erlang.float_to_binary(amount, decimals: 2)
152
153 defp format_decimal(%Decimal{} = decimal) do
154 decimal
155 |> Decimal.round(2)
156
:-(
|> Decimal.to_string()
157 end
158
159 defp format_currency(amount, currency) when currency in ["USD", "SGD", "AED"] do
160 case currency do
161 "USD" -> "$#{format_amount(amount)}"
162 "SGD" -> "S$#{format_amount(amount)}"
163 "AED" -> "AED #{format_amount(amount)}"
164 _ -> "#{format_amount(amount)} #{currency}"
165 end
166 end
167 defp format_currency(amount, currency), do: "#{format_amount(amount)} #{currency || "INR"}"
168
169 defp format_number(number) when is_integer(number) do
170 number
171 |> Integer.to_string()
172 |> String.reverse()
173 |> String.replace(~r/(\d{3})(?=\d)/, "\\1,")
174
:-(
|> String.reverse()
175 end
176
:-(
defp format_number(number), do: to_string(number)
177
178 defp calculate_percentage(part, total) when total > 0 do
179
:-(
Float.round(part / total * 100, 1)
180 end
181
:-(
defp calculate_percentage(_, _), do: 0.0
182
183
:-(
defp period_options do
184 [
185 {"Last 7 Days", "7_days"},
186 {"Last 30 Days", "30_days"},
187 {"Last 90 Days", "90_days"},
188 {"Last Year", "1_year"},
189 {"Custom Range", "custom"}
190 ]
191 end
192
193 defp prepare_chart_data(trends) do
194 # Prepare data for chart.js or similar charting library
195 trends
196 |> Enum.map(fn {date, count} ->
197 %{
198 date: Date.to_iso8601(date),
199 value: count
200 }
201 end)
202 |> Jason.encode!()
203 end
204
205 defp get_trend_comparison(current_data, previous_data) do
206 current_total = Enum.reduce(current_data, 0, fn {_, count}, acc -> acc + count end)
207 previous_total = Enum.reduce(previous_data, 0, fn {_, count}, acc -> acc + count end)
208
209 if previous_total > 0 do
210 change_percent = ((current_total - previous_total) / previous_total * 100) |> Float.round(1)
211 {current_total - previous_total, change_percent}
212 else
213 {current_total, 0.0}
214 end
215 end
216
217 # Helper functions for authentication and access control
218
:-(
defp get_current_user(session) do
219
:-(
case session do
220 %{"user_id" => user_id} when is_binary(user_id) or is_integer(user_id) ->
221
:-(
DaProductApp.Accounts.get_user!(user_id)
222
:-(
_ ->
223 nil
224 end
225 rescue
226
:-(
_ -> nil
227 end
228
229
:-(
defp has_access?(_user), do: true
230 end
Line Hits Source