defmodule DaProductAppWeb.ParameterManagementLive.Logs do use DaProductAppWeb, :live_view alias DaProductApp.ParameterManagement alias DaProductApp.TerminalManagement @impl true def mount(_params, _session, socket) do {:ok, assign(socket, # Core data logs: [], filtered_logs: [], parameter_templates: [], # Filters filters: %{ device_serial: "", status: "", push_type: "", template_id: "", date_range: "7d", limit: 50 }, # UI state show_filters: false, show_log_details: false, selected_log: nil, loading: false, # Stats log_stats: %{ total: 0, successful: 0, failed: 0, pending: 0 }, # Pagination page: 1, per_page: 50, total_count: 0, current_page: "parameter_logs" )} end @impl true def handle_params(_params, _url, socket) do # Load initial data socket = socket |> load_parameter_templates() |> load_logs() |> calculate_stats() {:noreply, socket} end # Filter Events @impl true def handle_event("toggle_filters", _params, socket) do {:noreply, assign(socket, show_filters: !socket.assigns.show_filters)} end def handle_event("update_filter", %{"field" => field, "value" => value}, socket) do filters = Map.put(socket.assigns.filters, String.to_atom(field), value) socket = socket |> assign(filters: filters) |> load_logs() |> calculate_stats() {:noreply, socket} end def handle_event("apply_filters", params, socket) do filters = extract_filters(params) socket = socket |> assign(filters: filters, page: 1) |> load_logs() |> calculate_stats() {:noreply, socket} end def handle_event("reset_filters", _params, socket) do default_filters = %{ device_serial: "", status: "", push_type: "", template_id: "", date_range: "7d", limit: 50 } socket = socket |> assign(filters: default_filters, page: 1) |> load_logs() |> calculate_stats() {:noreply, socket} end # Log Detail Events def handle_event("show_log_details", %{"log_id" => log_id}, socket) do log_id = String.to_integer(log_id) log = ParameterManagement.get_parameter_push_log!(log_id) {:noreply, assign(socket, show_log_details: true, selected_log: log )} end def handle_event("hide_log_details", _params, socket) do {:noreply, assign(socket, show_log_details: false, selected_log: nil )} end # Export Events def handle_event("export_logs", %{"format" => format}, socket) do case export_logs(socket.assigns.filtered_logs, format) do {:ok, file_content} -> filename = "parameter_logs_#{Date.utc_today()}.#{format}" socket = socket |> put_flash(:info, "Logs exported successfully as #{filename}") |> push_event("download", %{ filename: filename, content: file_content, mime_type: get_mime_type(format) }) {:noreply, socket} {:error, reason} -> {:noreply, put_flash(socket, :error, "Export failed: #{reason}")} end end # Pagination Events def handle_event("change_page", %{"page" => page}, socket) do page = String.to_integer(page) socket = socket |> assign(page: page) |> load_logs() {:noreply, socket} end # Refresh Events def handle_event("refresh_logs", _params, socket) do socket = socket |> assign(loading: true) |> load_logs() |> calculate_stats() |> assign(loading: false) {:noreply, put_flash(socket, :info, "Logs refreshed successfully")} end # Action Events def handle_event("retry_push", %{"log_id" => log_id}, socket) do log_id = String.to_integer(log_id) log = ParameterManagement.get_parameter_push_log!(log_id) case retry_parameter_push(log) do {:ok, new_log} -> socket = socket |> load_logs() |> put_flash(:info, "Parameter push retried successfully (Job ID: #{new_log.id})") {:noreply, socket} {:error, reason} -> {:noreply, put_flash(socket, :error, "Retry failed: #{inspect(reason)}")} end end # Catch-all for unhandled events def handle_event(event, params, socket) do IO.puts("DEBUG: Unhandled event in Parameter Logs: #{event}") IO.inspect(params, label: "params") {:noreply, socket} end # Private helper functions defp load_parameter_templates(socket) do templates = ParameterManagement.list_parameter_templates(%{is_active: true}) assign(socket, parameter_templates: templates) end defp load_logs(socket) do filters = socket.assigns.filters page = socket.assigns.page per_page = socket.assigns.per_page # Convert filters to the format expected by the backend query_filters = build_query_filters(filters) # Load logs with pagination logs = ParameterManagement.list_recent_parameter_push_logs(query_filters) # Apply pagination total_count = length(logs) start_index = (page - 1) * per_page end_index = min(start_index + per_page, total_count) paginated_logs = logs |> Enum.slice(start_index, per_page) assign(socket, logs: logs, filtered_logs: paginated_logs, total_count: total_count ) end defp calculate_stats(socket) do logs = socket.assigns.logs stats = %{ total: length(logs), successful: Enum.count(logs, &(&1.status == "acknowledged")), failed: Enum.count(logs, &(&1.status == "failed")), pending: Enum.count(logs, &(&1.status in ["pending", "sent"])) } assign(socket, log_stats: stats) end defp extract_filters(params) do %{ device_serial: Map.get(params, "device_serial", ""), status: Map.get(params, "status", ""), push_type: Map.get(params, "push_type", ""), template_id: Map.get(params, "template_id", ""), date_range: Map.get(params, "date_range", "7d"), limit: String.to_integer(Map.get(params, "limit", "50")) } end defp build_query_filters(filters) do query_filters = %{limit: filters.limit} # Add non-empty filters query_filters = if filters.device_serial != "", do: Map.put(query_filters, :device_sn, filters.device_serial), else: query_filters query_filters = if filters.status != "", do: Map.put(query_filters, :status, filters.status), else: query_filters query_filters = if filters.push_type != "", do: Map.put(query_filters, :push_type, filters.push_type), else: query_filters # Handle date range query_filters = case filters.date_range do "1d" -> Map.put(query_filters, :hours, 24) "3d" -> Map.put(query_filters, :hours, 72) "7d" -> Map.put(query_filters, :hours, 168) "30d" -> Map.put(query_filters, :hours, 720) _ -> query_filters end query_filters end defp export_logs(logs, format) do case format do "csv" -> export_csv(logs) "json" -> export_json(logs) _ -> {:error, "Unsupported format"} end end defp export_csv(logs) do headers = ["ID", "Device Serial", "Template", "Status", "Push Type", "Created At", "Sent At", "Acknowledged At", "Error Message"] rows = Enum.map(logs, fn log -> [ log.id, log.terminal.serial_number, (log.template && log.template.name) || "N/A", log.status, log.push_type, format_datetime(log.inserted_at), format_datetime(log.sent_at), format_datetime(log.acknowledged_at), log.error_message || "" ] end) csv_content = [headers | rows] |> Enum.map(&Enum.join(&1, ",")) |> Enum.join("\n") {:ok, csv_content} end defp export_json(logs) do json_data = Enum.map(logs, fn log -> %{ id: log.id, device_serial: log.terminal.serial_number, template_name: (log.template && log.template.name) || nil, status: log.status, push_type: log.push_type, created_at: log.inserted_at, sent_at: log.sent_at, acknowledged_at: log.acknowledged_at, error_message: log.error_message, parameters_sent: log.parameters_sent } end) case Jason.encode(json_data, pretty: true) do {:ok, json_string} -> {:ok, json_string} {:error, reason} -> {:error, reason} end end defp get_mime_type("csv"), do: "text/csv" defp get_mime_type("json"), do: "application/json" defp get_mime_type(_), do: "text/plain" defp format_datetime(nil), do: "" defp format_datetime(datetime) do datetime |> DateTime.truncate(:second) |> DateTime.to_string() end defp retry_parameter_push(log) do case TerminalManagement.get_terminal_by_serial(log.terminal.serial_number) do nil -> {:error, "Terminal not found"} terminal -> if log.template_id do ParameterManagement.apply_template_to_terminal(terminal, log.template_id, []) else ParameterManagement.push_parameters_to_terminal(terminal, []) end end end defp status_color("acknowledged"), do: "text-green-600 bg-green-100" defp status_color("failed"), do: "text-red-600 bg-red-100" defp status_color("pending"), do: "text-yellow-600 bg-yellow-100" defp status_color("sent"), do: "text-blue-600 bg-blue-100" defp status_color(_), do: "text-gray-600 bg-gray-100" defp status_icon("acknowledged"), do: "hero-check-circle" defp status_icon("failed"), do: "hero-x-circle" defp status_icon("pending"), do: "hero-clock" defp status_icon("sent"), do: "hero-paper-airplane" defp status_icon(_), do: "hero-question-mark-circle" end