mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-20 13:49:39 +02:00
Fix AND prefix on rule form (#2234)
* Fix AND prefix on rule form This new condition prefix data target is used to ensure the AND prefix is added/removed to additional conditions/groups when there aren't any saved conditions yet. * Ensure the condition group "Add condition" button is type button to avoid form submission * Add prefix update when removing a subcondition * Use data condition to update the prefix on conditions, condition groups, and subconditions * Use condition remove instead of element remove for condition groups so prefix logic runs * Add back explicit show_prefixes to ensure subconditions never have a prefix * Run the prefix update runs on a load of a form, which handles prefixes on an edit since no conditions change * Ensure saved items that are marked for removal don't impact the index * Simplify logic since we don't process subconditions * Clean up comments * Add primary_condition_title field to rule model
This commit is contained in:
parent
df8e22afe9
commit
30d3eef67f
7 changed files with 70 additions and 18 deletions
|
@ -21,12 +21,23 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(e) {
|
remove(e) {
|
||||||
|
// Find the parent rules controller before removing the condition
|
||||||
|
const rulesEl = this.element.closest('[data-controller~="rules"]');
|
||||||
|
|
||||||
if (e.params.destroy) {
|
if (e.params.destroy) {
|
||||||
this.destroyFieldTarget.value = true;
|
this.destroyFieldTarget.value = true;
|
||||||
this.element.classList.add("hidden");
|
this.element.classList.add("hidden");
|
||||||
} else {
|
} else {
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the prefixes of all conditions from the parent rules controller
|
||||||
|
if (rulesEl) {
|
||||||
|
const rulesController = this.application.getControllerForElementAndIdentifier(rulesEl, "rules");
|
||||||
|
if (rulesController && typeof rulesController.updateConditionPrefixes === "function") {
|
||||||
|
rulesController.updateConditionPrefixes();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConditionTypeChange(e) {
|
handleConditionTypeChange(e) {
|
||||||
|
|
|
@ -11,11 +11,17 @@ export default class extends Controller {
|
||||||
"effectiveDateInput",
|
"effectiveDateInput",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// Update condition prefixes on first connection (form render on edit)
|
||||||
|
this.updateConditionPrefixes();
|
||||||
|
}
|
||||||
|
|
||||||
addConditionGroup() {
|
addConditionGroup() {
|
||||||
this.#appendTemplate(
|
this.#appendTemplate(
|
||||||
this.conditionGroupTemplateTarget,
|
this.conditionGroupTemplateTarget,
|
||||||
this.conditionsListTarget,
|
this.conditionsListTarget,
|
||||||
);
|
);
|
||||||
|
this.updateConditionPrefixes();
|
||||||
}
|
}
|
||||||
|
|
||||||
addCondition() {
|
addCondition() {
|
||||||
|
@ -23,6 +29,7 @@ export default class extends Controller {
|
||||||
this.conditionTemplateTarget,
|
this.conditionTemplateTarget,
|
||||||
this.conditionsListTarget,
|
this.conditionsListTarget,
|
||||||
);
|
);
|
||||||
|
this.updateConditionPrefixes();
|
||||||
}
|
}
|
||||||
|
|
||||||
addAction() {
|
addAction() {
|
||||||
|
@ -45,4 +52,27 @@ export default class extends Controller {
|
||||||
#uniqueKey() {
|
#uniqueKey() {
|
||||||
return Date.now();
|
return Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates the prefix visibility of all conditions and condition groups
|
||||||
|
// This is also called by the rule/conditions_controller when a subcondition is removed
|
||||||
|
updateConditionPrefixes() {
|
||||||
|
const conditions = Array.from(this.conditionsListTarget.children);
|
||||||
|
let conditionIndex = 0;
|
||||||
|
|
||||||
|
conditions.forEach((condition) => {
|
||||||
|
// Only process visible conditions, this prevents conditions that are marked for removal and hidden
|
||||||
|
// from being added to the index. This is important when editing a rule.
|
||||||
|
if (!condition.classList.contains('hidden')) {
|
||||||
|
const prefixEl = condition.querySelector('[data-condition-prefix]');
|
||||||
|
if (prefixEl) {
|
||||||
|
if (conditionIndex === 0) {
|
||||||
|
prefixEl.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
prefixEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
conditionIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,18 @@ class Rule < ApplicationRecord
|
||||||
RuleJob.perform_later(self, ignore_attribute_locks: ignore_attribute_locks)
|
RuleJob.perform_later(self, ignore_attribute_locks: ignore_attribute_locks)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def primary_condition_title
|
||||||
|
return "No conditions" if conditions.none?
|
||||||
|
|
||||||
|
first_condition = conditions.first
|
||||||
|
if first_condition.compound? && first_condition.sub_conditions.any?
|
||||||
|
first_sub_condition = first_condition.sub_conditions.first
|
||||||
|
"If #{first_sub_condition.filter.label.downcase} #{first_sub_condition.operator} #{first_sub_condition.value_display}"
|
||||||
|
else
|
||||||
|
"If #{first_condition.filter.label.downcase} #{first_condition.operator} #{first_condition.value_display}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def matching_resources_scope
|
def matching_resources_scope
|
||||||
scope = registry.resource_scope
|
scope = registry.resource_scope
|
||||||
|
|
|
@ -4,8 +4,11 @@
|
||||||
<% rule = condition.rule %>
|
<% rule = condition.rule %>
|
||||||
|
|
||||||
<li data-controller="rule--conditions" data-rule--conditions-condition-filters-value="<%= rule.condition_filters.to_json %>" class="flex items-center gap-3">
|
<li data-controller="rule--conditions" data-rule--conditions-condition-filters-value="<%= rule.condition_filters.to_json %>" class="flex items-center gap-3">
|
||||||
<% if form.index.to_i > 0 && show_prefix %>
|
|
||||||
<div class="pl-4">
|
<%# Conditionally render the prefix %>
|
||||||
|
<%# Condition groups pass in show_prefix: false for subconditions since the ANY/ALL selector makes that clear %>
|
||||||
|
<% if show_prefix %>
|
||||||
|
<div class="pl-2" data-condition-prefix>
|
||||||
<span class="font-medium uppercase text-xs">and</span>
|
<span class="font-medium uppercase text-xs">and</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -3,17 +3,16 @@
|
||||||
<% condition = form.object %>
|
<% condition = form.object %>
|
||||||
<% rule = condition.rule %>
|
<% rule = condition.rule %>
|
||||||
|
|
||||||
<li data-controller="rule--conditions element-removal" class="border border-secondary rounded-md p-4 space-y-3">
|
<li data-controller="rule--conditions" class="border border-secondary rounded-md p-4 space-y-3">
|
||||||
|
|
||||||
<%= form.hidden_field :condition_type, value: "compound" %>
|
<%= form.hidden_field :condition_type, value: "compound" %>
|
||||||
|
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<% unless form.index == 0 %>
|
<%# Show prefix on condition groups, except the first one %>
|
||||||
<div class="pl-2">
|
<div class="pl-2" data-condition-prefix>
|
||||||
<span class="font-medium uppercase text-xs">and</span>
|
<span class="font-medium uppercase text-xs">and</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
<p class="text-sm text-secondary">match</p>
|
<p class="text-sm text-secondary">match</p>
|
||||||
<%= form.select :operator, [["all", "and"], ["any", "or"]], { container_class: "w-fit" }, data: { rules_target: "operatorField" } %>
|
<%= form.select :operator, [["all", "and"], ["any", "or"]], { container_class: "w-fit" }, data: { rules_target: "operatorField" } %>
|
||||||
<p class="text-sm text-secondary">of the following conditions</p>
|
<p class="text-sm text-secondary">of the following conditions</p>
|
||||||
|
@ -21,16 +20,16 @@
|
||||||
|
|
||||||
<%= icon(
|
<%= icon(
|
||||||
"trash-2",
|
"trash-2",
|
||||||
size: "sm",
|
|
||||||
as_button: true,
|
as_button: true,
|
||||||
data: { action: "element-removal#remove" }
|
size: "sm",
|
||||||
|
data: { action: "rule--conditions#remove" }
|
||||||
) %>
|
) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Sub-condition template, used by Stimulus controller to add new sub-conditions dynamically %>
|
<%# Sub-condition template, used by Stimulus controller to add new sub-conditions dynamically %>
|
||||||
<template data-rule--conditions-target="subConditionTemplate">
|
<template data-rule--conditions-target="subConditionTemplate">
|
||||||
<%= form.fields_for :sub_conditions, Rule::Condition.new(parent: condition, condition_type: rule.condition_filters.first.key), child_index: "IDX_PLACEHOLDER" do |scf| %>
|
<%= form.fields_for :sub_conditions, Rule::Condition.new(parent: condition, condition_type: rule.condition_filters.first.key), child_index: "IDX_PLACEHOLDER" do |scf| %>
|
||||||
<%= render "rule/conditions/condition", form: scf %>
|
<%= render "rule/conditions/condition", form: scf, show_prefix: false %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -44,6 +43,7 @@
|
||||||
text: "Add condition",
|
text: "Add condition",
|
||||||
leading_icon: "plus",
|
leading_icon: "plus",
|
||||||
variant: "ghost",
|
variant: "ghost",
|
||||||
|
type: "button",
|
||||||
data: { action: "rule--conditions#addSubCondition" }
|
data: { action: "rule--conditions#addSubCondition" }
|
||||||
) %>
|
) %>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<section class="space-y-4">
|
<section class="space-y-4">
|
||||||
<h3 class="text-sm font-medium text-primary">If <%= rule.resource_type %></h3>
|
<h3 class="text-sm font-medium text-primary">If <%= rule.resource_type %></h3>
|
||||||
|
|
||||||
<%# Condition template, used by Stimulus controller to add new conditions dynamically %>
|
<%# Condition Group template, used by Stimulus controller to add new conditions dynamically %>
|
||||||
<template data-rules-target="conditionGroupTemplate">
|
<template data-rules-target="conditionGroupTemplate">
|
||||||
<%= f.fields_for :conditions, Rule::Condition.new(rule: rule, condition_type: "compound", operator: "and"), child_index: "IDX_PLACEHOLDER" do |cf| %>
|
<%= f.fields_for :conditions, Rule::Condition.new(rule: rule, condition_type: "compound", operator: "and"), child_index: "IDX_PLACEHOLDER" do |cf| %>
|
||||||
<%= render "rule/conditions/condition_group", form: cf %>
|
<%= render "rule/conditions/condition_group", form: cf %>
|
||||||
|
|
|
@ -4,12 +4,8 @@
|
||||||
<div class="text-sm space-y-1.5">
|
<div class="text-sm space-y-1.5">
|
||||||
<% if rule.conditions.any? %>
|
<% if rule.conditions.any? %>
|
||||||
<p class="flex items-center flex-wrap gap-1.5">
|
<p class="flex items-center flex-wrap gap-1.5">
|
||||||
<span class="px-2 py-1 border border-alpha-black-200 rounded-full">
|
<span class="px-2 py-1 border border-secondary rounded-full">
|
||||||
<% if rule.conditions.first.compound? %>
|
<%= rule.primary_condition_title %>
|
||||||
If <%= rule.conditions.first.sub_conditions.first.filter.label %> <%= rule.conditions.first.sub_conditions.first.operator %> <%= rule.conditions.first.sub_conditions.first.value_display %>
|
|
||||||
<% else %>
|
|
||||||
If <%= rule.conditions.first.filter.label %> <%= rule.conditions.first.operator %> <%= rule.conditions.first.value_display %>
|
|
||||||
<% end %>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<% if rule.conditions.count > 1 %>
|
<% if rule.conditions.count > 1 %>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue