defmodule DaProductAppWeb.TerminalGroupLive.Index do use DaProductAppWeb, :live_view require Logger alias DaProductApp.TerminalManagement @impl true def mount(_params, _session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(DaProductApp.PubSub, "tms:groups") Phoenix.PubSub.subscribe(DaProductApp.PubSub, "terminal:events") end groups = TerminalManagement.list_terminal_groups() statistics = TerminalManagement.get_group_statistics() {:ok, assign(socket, groups: groups, statistics: statistics, current_page: "terminal_groups", selected_group: nil, show_new_group_slider: false, show_edit_group_slider: false, show_rules_slider: false, show_assign_terminals_slider: false, new_group_form: %{}, edit_group_form: %{}, new_rule_form: %{}, group_rules: [], group_terminals: [], available_terminals: [], filters: %{"group_type" => "all", "active_only" => true}, errors: %{}, applying_rules: false, last_event: nil, auto_events_enabled: true )} end @impl true def handle_params(params, _url, socket) do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end defp apply_action(socket, :index, _params) do socket |> assign(:page_title, "Terminal Groups") end defp apply_action(socket, :show, %{"id" => id}) do case TerminalManagement.get_terminal_group(id) do {:ok, group} -> rules = TerminalManagement.list_group_rules(group.id) terminals = TerminalManagement.get_group_terminals(group.id) socket |> assign(:page_title, "Group: #{group.name}") |> assign(:selected_group, group) |> assign(:group_rules, rules) |> assign(:group_terminals, terminals) {:error, :not_found} -> socket |> put_flash(:error, "Group not found") |> redirect(to: ~p"/terminals/groups") end end @impl true def handle_event("filter", %{"filters" => filters}, socket) do groups = TerminalManagement.list_terminal_groups(filters) {:noreply, assign(socket, groups: groups, filters: filters )} end def handle_event("show_new_group", _params, socket) do {:noreply, assign(socket, show_new_group_slider: true, new_group_form: %{ "name" => "", "description" => "", "group_type" => "custom", "color" => "#3B82F6", "icon" => "hero-squares-2x2" }, errors: %{} )} end def handle_event("hide_new_group", _params, socket) do {:noreply, assign(socket, show_new_group_slider: false, new_group_form: %{}, errors: %{} )} end def handle_event("update_new_group_form", %{"group" => group_params}, socket) do {:noreply, assign(socket, new_group_form: group_params)} end def handle_event("create_group", %{"group" => group_params}, socket) do attrs = Map.put(group_params, "created_by", "current_user") # Replace with actual user case TerminalManagement.create_terminal_group(attrs) do {:ok, _group} -> groups = TerminalManagement.list_terminal_groups(socket.assigns.filters) statistics = TerminalManagement.get_group_statistics() {:noreply, socket |> assign(groups: groups, statistics: statistics) |> assign(show_new_group_slider: false, new_group_form: %{}, errors: %{}) |> put_flash(:info, "Group created successfully") } {:error, changeset} -> errors = changeset.errors |> Enum.into(%{}, fn {field, {message, _}} -> {Atom.to_string(field), message} end) {:noreply, assign(socket, errors: errors)} end end def handle_event("edit_group", %{"id" => id}, socket) do case TerminalManagement.get_terminal_group(id) do {:ok, group} -> form = %{ "name" => group.name, "description" => group.description || "", "color" => group.color, "icon" => group.icon } {:noreply, assign(socket, selected_group: group, show_edit_group_slider: true, edit_group_form: form, errors: %{} )} {:error, :not_found} -> {:noreply, put_flash(socket, :error, "Group not found")} end end def handle_event("update_edit_group_form", %{"group" => group_params}, socket) do {:noreply, assign(socket, edit_group_form: group_params)} end def handle_event("update_group", %{"group" => group_params}, socket) do group = socket.assigns.selected_group case TerminalManagement.update_terminal_group(group, group_params) do {:ok, _updated_group} -> groups = TerminalManagement.list_terminal_groups(socket.assigns.filters) {:noreply, socket |> assign(groups: groups) |> assign(show_edit_group_slider: false, edit_group_form: %{}, errors: %{}) |> put_flash(:info, "Group updated successfully") } {:error, changeset} -> errors = changeset.errors |> Enum.into(%{}, fn {field, {message, _}} -> {Atom.to_string(field), message} end) {:noreply, assign(socket, errors: errors)} end end def handle_event("delete_group", %{"id" => id}, socket) do case TerminalManagement.get_terminal_group(id) do {:ok, group} -> case TerminalManagement.delete_terminal_group(group) do {:ok, _} -> groups = TerminalManagement.list_terminal_groups(socket.assigns.filters) statistics = TerminalManagement.get_group_statistics() {:noreply, socket |> assign(groups: groups, statistics: statistics) |> put_flash(:info, "Group deleted successfully") } {:error, :cannot_delete_system_group} -> {:noreply, put_flash(socket, :error, "Cannot delete system groups")} {:error, _} -> {:noreply, put_flash(socket, :error, "Failed to delete group")} end {:error, :not_found} -> {:noreply, put_flash(socket, :error, "Group not found")} end end def handle_event("show_rules", %{"id" => id}, socket) do case TerminalManagement.get_terminal_group(id) do {:ok, group} -> rules = TerminalManagement.list_group_rules(group.id) {:noreply, assign(socket, selected_group: group, group_rules: rules, show_rules_slider: true, new_rule_form: %{ "rule_name" => "", "rule_type" => "field_match", "field_name" => "vendor", "operator" => "equals", "value" => "", "priority" => "100" } )} {:error, :not_found} -> {:noreply, put_flash(socket, :error, "Group not found")} end end def handle_event("hide_rules", _params, socket) do {:noreply, assign(socket, show_rules_slider: false, selected_group: nil, group_rules: [], new_rule_form: %{} )} end def handle_event("update_rule_form", %{"rule" => rule_params}, socket) do {:noreply, assign(socket, new_rule_form: rule_params)} end def handle_event("apply_rule_template", %{"template" => template}, socket) do form = case template do "vendor_ingenico" -> %{ "rule_name" => "Ingenico Terminals", "rule_type" => "field_match", "field_name" => "vendor", "operator" => "equals", "value" => "Ingenico", "priority" => "100" } "status_active" -> %{ "rule_name" => "Active Terminals", "rule_type" => "field_match", "field_name" => "status", "operator" => "equals", "value" => "active", "priority" => "50" } "high_tier" -> %{ "rule_name" => "High Tier Terminals", "rule_type" => "field_match", "field_name" => "tier", "operator" => "in", "value" => "premium,enterprise,high", "priority" => "75" } "production_deployment" -> %{ "rule_name" => "Production Deployment", "rule_type" => "field_match", "field_name" => "deployment_type", "operator" => "equals", "value" => "production", "priority" => "25" } _ -> socket.assigns.new_rule_form end {:noreply, assign(socket, new_rule_form: form)} end def handle_event("create_rule", %{"rule" => rule_params}, socket) do group = socket.assigns.selected_group attrs = Map.put(rule_params, "group_id", group.id) # Validate rule before creating case validate_rule_params(rule_params) do {:ok, _} -> case TerminalManagement.create_group_rule(attrs) do {:ok, _rule} -> rules = TerminalManagement.list_group_rules(group.id) statistics = TerminalManagement.get_group_statistics() groups = TerminalManagement.list_terminal_groups(socket.assigns.filters) {:noreply, socket |> assign(group_rules: rules, statistics: statistics, groups: groups) |> assign(new_rule_form: %{ "rule_name" => "", "rule_type" => "field_match", "field_name" => "vendor", "operator" => "equals", "value" => "", "priority" => "100" }) |> put_flash(:info, "Rule created and applied successfully") } {:error, changeset} -> errors = changeset.errors |> Enum.into(%{}, fn {field, {message, _}} -> {Atom.to_string(field), message} end) {:noreply, assign(socket, errors: errors)} end {:error, message} -> {:noreply, put_flash(socket, :error, message)} end end def handle_event("delete_rule", %{"id" => _rule_id}, socket) do # Implementation for deleting rules {:noreply, put_flash(socket, :info, "Rule deletion not implemented yet")} end def handle_event("apply_all_rules", _params, socket) do # Set loading state and then process in the background send(self(), :process_apply_rules) {:noreply, assign(socket, applying_rules: true)} end def handle_info(:process_apply_rules, socket) do start_time = System.monotonic_time(:millisecond) Logger.info("Starting rule application process for all terminals") case TerminalManagement.apply_all_rules() do {:ok, count} -> end_time = System.monotonic_time(:millisecond) duration = end_time - start_time Logger.info("Successfully applied #{count} rules to all terminals in #{duration}ms") # Refresh both statistics and groups list to update terminal counts statistics = TerminalManagement.get_group_statistics() groups = TerminalManagement.list_terminal_groups(socket.assigns.filters) {:noreply, socket |> assign(statistics: statistics, groups: groups, applying_rules: false) |> put_flash(:info, "Applied #{count} rules to all terminals (#{duration}ms)") } {:error, reason} -> Logger.error("Failed to apply rules: #{inspect(reason)}") {:noreply, socket |> assign(applying_rules: false) |> put_flash(:error, "Failed to apply rules - check logs for details") } end end def handle_info({:event_processed, event}, socket) do # Real-time update when rules are automatically applied groups = TerminalManagement.list_terminal_groups(socket.assigns.filters) statistics = TerminalManagement.get_group_statistics() {:noreply, socket |> assign(groups: groups, statistics: statistics, last_event: event) |> put_flash(:info, "✨ Auto-applied rules for #{event.type} event") } end def handle_info({:group_change, _event}, socket) do # Refresh data when groups change groups = TerminalManagement.list_terminal_groups(socket.assigns.filters) statistics = TerminalManagement.get_group_statistics() {:noreply, assign(socket, groups: groups, statistics: statistics)} end def handle_event("refresh_counts", _params, socket) do # Preserve filters when refreshing filters = socket.assigns.filters groups = TerminalManagement.list_terminal_groups(filters) statistics = TerminalManagement.get_group_statistics() {:noreply, socket |> assign(groups: groups, statistics: statistics, filters: filters) |> put_flash(:info, "Terminal counts refreshed") } end def handle_event("close_slide_over", _params, socket) do {:noreply, assign(socket, show_new_group_slider: false, show_edit_group_slider: false, show_rules_slider: false, show_assign_terminals_slider: false, new_group_form: %{}, edit_group_form: %{}, new_rule_form: %{}, errors: %{}, applying_rules: false )} end @impl true def render(assigns) do ~H"""
Organize terminals into logical groups with automated rules
| Group | Type | Terminals | Rules | Status | Actions |
|---|---|---|---|---|---|
|
<.icon name={group.icon} class="w-4 h-4" style={"color: #{group.color}"} />
<%= group.name %>
<%= group.description %>
|
<%= String.capitalize(group.group_type) %> | <%= group.terminal_count || 0 %> | <%= length(group.group_rules || []) %> | <%= if group.is_active, do: "Active", else: "Inactive" %> |
<%= unless group.group_type == "system" do %>
<% end %>
|
<%= String.capitalize(rule.rule_type) %> - <%= rule.field_name %> <%= rule.operator %> "<%= rule.value %>"
Priority: <%= rule.priority %>
No rules defined yet
<% end %>