cover/Elixir.DaProductAppWeb.TransactionsLive.html

1
:-(
defmodule DaProductAppWeb.TransactionsLive do
2 @moduledoc """
3 LiveView for Transaction monitoring and analytics.
4
5 Features:
6 - Read-only transaction monitoring
7 - Search and filter capabilities
8 - Real-time transaction status updates
9 - Transaction details and history
10 - Performance analytics
11 """
12 use DaProductAppWeb, :live_view
13
14 alias DaProductApp.Accounts
15 alias DaProductApp.Repo
16 alias DaProductApp.Transactions.{ReqPay, ReqChkTxn}
17 alias DaProductApp.Monitoring
18
19 @impl true
20
:-(
def mount(_params, session, socket) do
21
:-(
case get_current_user(session) do
22
:-(
nil ->
23 {:ok, redirect(socket, to: ~p"/login")}
24
25 user ->
26
:-(
if has_access?(user) do
27
:-(
if connected?(socket) do
28 # Subscribe to transaction events for real-time updates
29
:-(
Phoenix.PubSub.subscribe(DaProductApp.PubSub, "transactions")
30 end
31
32
:-(
socket =
33 socket
34 |> assign(:current_user, user)
35 |> assign(:page_title, "Transaction Monitoring")
36 |> assign(:current_page, :transactions)
37 |> assign(:transactions, [])
38 |> assign(:search_term, "")
39 |> assign(:status_filter, :all)
40 |> assign(:state_filter, :all)
41 |> assign(:page, 1)
42 |> assign(:page_size, 50)
43 |> assign(:has_more, false)
44 |> assign(:date_from, nil)
45 |> assign(:date_to, nil)
46 |> assign(:selected_transaction, nil)
47 |> assign(:show_details_modal, false)
48 |> assign(:stats, %{})
49 |> load_transactions()
50 |> load_stats()
51
52 {:ok, socket}
53 else
54 {:ok,
55 socket
56 |> put_flash(:error, "Access denied")
57 |> redirect(to: ~p"/login")}
58 end
59 end
60 end
61
62 @impl true
63
:-(
def handle_params(params, _url, socket) do
64
:-(
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
65 end
66
67 defp apply_action(socket, :index, _params) do
68 socket
69 |> assign(:page_title, "Transaction Monitoring")
70
:-(
|> assign(:show_details_modal, false)
71 end
72
73 defp apply_action(socket, :show, %{"id" => id}) do
74
:-(
transaction = Monitoring.get_transaction!(id)
75 # Try to find linked ReqPay and ReqChkTxn by transaction_id so we can link to their show pages
76
:-(
req_pay = Repo.get_by(ReqPay, transaction_id: transaction.id)
77
:-(
req_chk = Repo.get_by(ReqChkTxn, transaction_id: transaction.id)
78
79 socket
80
:-(
|> assign(:page_title, "Transaction #{transaction.org_txn_id}")
81 |> assign(:show_details_modal, true)
82 |> assign(:selected_transaction, transaction)
83
:-(
|> assign(:selected_req_pay_id, if(req_pay, do: req_pay.id, else: nil))
84
:-(
|> assign(:selected_req_chk_txn_id, if(req_chk, do: req_chk.id, else: nil))
85 end
86
87 @impl true
88
:-(
def handle_event("search", %{"search_term" => search_term}, socket) do
89
:-(
socket =
90 socket
91 |> assign(:search_term, search_term)
92 |> assign(:page, 1)
93 |> load_transactions()
94
95 {:noreply, socket}
96 end
97
98
:-(
def handle_event("filter_status", %{"status" => status}, socket) do
99
:-(
status_filter = if status == "all", do: :all, else: String.to_atom(status)
100
101
:-(
socket =
102 socket
103 |> assign(:status_filter, status_filter)
104 |> assign(:page, 1)
105 |> load_transactions()
106
107 {:noreply, socket}
108 end
109
110
:-(
def handle_event("filter_state", %{"state" => state}, socket) do
111
:-(
state_filter = if state == "all", do: :all, else: state
112
113
:-(
socket =
114 socket
115 |> assign(:state_filter, state_filter)
116 |> assign(:page, 1)
117 |> load_transactions()
118
119 {:noreply, socket}
120 end
121
122
:-(
def handle_event("filter_date_range", %{"date_from" => date_from, "date_to" => date_to}, socket) do
123
:-(
parsed_date_from = if date_from != "", do: Date.from_iso8601!(date_from), else: nil
124
:-(
parsed_date_to = if date_to != "", do: Date.from_iso8601!(date_to), else: nil
125
126
:-(
socket =
127 socket
128 |> assign(:date_from, parsed_date_from)
129 |> assign(:date_to, parsed_date_to)
130 |> assign(:page, 1)
131 |> load_transactions()
132 |> load_stats()
133
134 {:noreply, socket}
135 end
136
137
:-(
def handle_event("close_details", _params, socket) do
138 {:noreply, push_patch(socket, to: ~p"/transactions")}
139 end
140
141
:-(
def handle_event("refresh", _params, socket) do
142
:-(
socket =
143 socket
144 |> assign(:page, 1)
145 |> load_transactions()
146 |> load_stats()
147 |> put_flash(:info, "Data refreshed")
148
149 {:noreply, socket}
150 end
151
152
:-(
def handle_event("clear_filters", _params, socket) do
153
:-(
socket =
154 socket
155 |> assign(:search_term, "")
156 |> assign(:status_filter, :all)
157 |> assign(:state_filter, :all)
158 |> assign(:date_from, nil)
159 |> assign(:date_to, nil)
160 |> assign(:page, 1)
161 |> load_transactions()
162 |> load_stats()
163
164 {:noreply, socket}
165 end
166
167
:-(
def handle_event("prev_page", _params, socket) do
168
:-(
page = max(socket.assigns.page - 1, 1)
169
170
:-(
socket =
171 socket
172 |> assign(:page, page)
173 |> load_transactions()
174
175 {:noreply, socket}
176 end
177
178
:-(
def handle_event("next_page", _params, socket) do
179 # Only advance if there are more results
180
:-(
if socket.assigns.has_more do
181
:-(
page = socket.assigns.page + 1
182
183
:-(
socket =
184 socket
185 |> assign(:page, page)
186 |> load_transactions()
187
188 {:noreply, socket}
189 else
190 {:noreply, socket}
191 end
192 end
193
194
:-(
def handle_event("set_page_size", %{"page_size" => page_size_str}, socket) do
195
:-(
page_size = case Integer.parse(page_size_str) do
196
:-(
{v, _} when v > 0 -> v
197
:-(
_ -> socket.assigns.page_size || 50
198 end
199
200
:-(
socket =
201 socket
202 |> assign(:page_size, page_size)
203 |> assign(:page, 1)
204 |> load_transactions()
205
206 {:noreply, socket}
207 end
208
209 # Handle real-time updates from PubSub
210 @impl true
211
:-(
def handle_info({:transaction_created, _transaction}, socket) do
212 {:noreply, load_transactions(socket)}
213 end
214
215
:-(
def handle_info({:transaction_updated, _transaction}, socket) do
216 {:noreply, load_transactions(socket)}
217 end
218
219
:-(
def handle_info({:transaction_status_changed, _transaction}, socket) do
220 {:noreply, load_transactions(socket)}
221 end
222
223 defp load_transactions(socket) do
224
:-(
filters = [
225
:-(
search_term: socket.assigns.search_term,
226
:-(
status: socket.assigns.status_filter,
227
:-(
state: socket.assigns.state_filter,
228
:-(
date_from: socket.assigns.date_from,
229
:-(
date_to: socket.assigns.date_to,
230
:-(
page: socket.assigns.page,
231
:-(
page_size: socket.assigns.page_size
232 ]
233
234
:-(
transactions = Monitoring.list_transactions(filters)
235
:-(
has_more = length(transactions) >= (socket.assigns.page_size || 50)
236
237 socket
238 |> assign(:transactions, transactions)
239
:-(
|> assign(:has_more, has_more)
240 end
241
242 defp load_stats(socket) do
243
:-(
stats = Monitoring.get_transaction_stats(socket.assigns.date_from, socket.assigns.date_to)
244
:-(
assign(socket, :stats, stats)
245 end
246
247 # Helper functions for the template
248
:-(
defp status_badge_class("success"), do: "badge-success"
249
:-(
defp status_badge_class("failure"), do: "badge-error"
250
:-(
defp status_badge_class("pending"), do: "badge-warning"
251
:-(
defp status_badge_class("processing"), do: "badge-info"
252
:-(
defp status_badge_class("deemed"), do: "badge-secondary"
253
:-(
defp status_badge_class("reversed"), do: "badge-neutral"
254
:-(
defp status_badge_class(_), do: "badge-ghost"
255
256
:-(
defp state_badge_class("success"), do: "badge-success"
257
:-(
defp state_badge_class("failure"), do: "badge-error"
258
:-(
defp state_badge_class("initiated"), do: "badge-info"
259
:-(
defp state_badge_class("npci_received"), do: "badge-info"
260
:-(
defp state_badge_class("credit_pending"), do: "badge-warning"
261
:-(
defp state_badge_class("reversed"), do: "badge-neutral"
262
:-(
defp state_badge_class(_), do: "badge-ghost"
263
264
:-(
defp format_amount(nil), do: "N/A"
265 defp format_amount(amount) when is_binary(amount) do
266
:-(
case Decimal.parse(amount) do
267
:-(
{decimal, _} -> format_decimal(decimal)
268
:-(
:error -> amount
269 end
270 end
271
:-(
defp format_amount(%Decimal{} = amount), do: format_decimal(amount)
272
:-(
defp format_amount(amount) when is_number(amount), do: :erlang.float_to_binary(amount, decimals: 2)
273
274 defp format_decimal(%Decimal{} = decimal) do
275 decimal
276 |> Decimal.round(2)
277
:-(
|> Decimal.to_string()
278 end
279
280 defp format_currency(amount, currency) when currency in ["USD", "SGD", "AED"] do
281
:-(
case currency do
282
:-(
"USD" -> "$#{format_amount(amount)}"
283
:-(
"SGD" -> "S$#{format_amount(amount)}"
284
:-(
"AED" -> "AED #{format_amount(amount)}"
285
:-(
_ -> "#{format_amount(amount)} #{currency}"
286 end
287 end
288
:-(
defp format_currency(amount, currency), do: "#{format_amount(amount)} #{currency || "INR"}"
289
290
:-(
defp transaction_status_options do
291 [
292 {"All Statuses", "all"},
293 {"Success", "success"},
294 {"Failure", "failure"},
295 {"Pending", "pending"},
296 {"Processing", "processing"},
297 {"Deemed", "deemed"},
298 {"Reversed", "reversed"}
299 ]
300 end
301
302
:-(
defp transaction_state_options do
303 [
304 {"All States", "all"},
305 {"Initiated", "initiated"},
306 {"NPCI Received", "npci_received"},
307 {"Debit Secured", "debit_secured"},
308 {"Credit Pending", "credit_pending"},
309 {"Partner Credit Initiated", "partner_credit_initiated"},
310 {"Credit Success", "credit_success"},
311 {"Success", "success"},
312 {"Failure", "failure"},
313 {"Reversed", "reversed"}
314 ]
315 end
316
317 # Helper functions for authentication and access control
318
:-(
defp get_current_user(session) do
319
:-(
case session do
320 %{"user_id" => user_id} when is_binary(user_id) or is_integer(user_id) ->
321
:-(
Accounts.get_user!(user_id)
322
:-(
_ ->
323 nil
324 end
325 rescue
326
:-(
_ -> nil
327 end
328
329
:-(
defp has_access?(_user), do: true
330 end
Line Hits Source