mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-10 07:55:21 +02:00
Clean up rules stimulus controller
This commit is contained in:
parent
8796450308
commit
fe8008e5ed
11 changed files with 118 additions and 85 deletions
|
@ -2,7 +2,7 @@ class RulesController < ApplicationController
|
||||||
before_action :set_rule, only: [ :show, :edit, :update, :destroy ]
|
before_action :set_rule, only: [ :show, :edit, :update, :destroy ]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@rules = Current.family.rules
|
@rules = Current.family.rules.order(created_at: :desc)
|
||||||
render layout: "settings"
|
render layout: "settings"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,7 @@ import { Controller } from "@hotwired/stimulus";
|
||||||
// Connects to data-controller="rule"
|
// Connects to data-controller="rule"
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static values = {
|
static values = {
|
||||||
conditionsRegistry: Array,
|
registry: Object,
|
||||||
actionsRegistry: Array,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static targets = [
|
static targets = [
|
||||||
|
@ -14,12 +13,13 @@ export default class extends Controller {
|
||||||
"condition",
|
"condition",
|
||||||
"actionsList",
|
"actionsList",
|
||||||
"action",
|
"action",
|
||||||
"destroyInput",
|
"destroyField",
|
||||||
|
"operatorField",
|
||||||
|
"valueField",
|
||||||
];
|
];
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
console.log(this.conditionsRegistryValue);
|
console.log(this.registryValue);
|
||||||
console.log(this.actionsRegistryValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addCondition() {
|
addCondition() {
|
||||||
|
@ -32,53 +32,18 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConditionTypeChange(e) {
|
handleConditionTypeChange(e) {
|
||||||
const definition = this.conditionsRegistryValue.find((def) => {
|
const definition = this.#getConditionFilterDefinition(e.target.value);
|
||||||
return def.condition_type === e.target.value;
|
const conditionEl = this.#getEventConditionEl(e.target);
|
||||||
});
|
const valueFieldEl = this.#getFieldEl(this.valueFieldTargets, conditionEl);
|
||||||
|
|
||||||
const conditionEl = this.conditionTargets.find((t) => {
|
this.#updateOperatorsField(definition, conditionEl);
|
||||||
return t.contains(e.target);
|
|
||||||
});
|
|
||||||
|
|
||||||
const operatorSelectEl = conditionEl.querySelector(
|
if (definition.type === "select") {
|
||||||
"select[data-id='operator-select']",
|
const selectEl = this.#buildSelectInput(definition, valueFieldEl);
|
||||||
);
|
valueFieldEl.replaceWith(selectEl);
|
||||||
|
|
||||||
operatorSelectEl.innerHTML = definition.operators
|
|
||||||
.map((operator) => {
|
|
||||||
return `<option value="${operator}">${operator}</option>`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
const valueInputEl = conditionEl.querySelector("[data-id='value-input']");
|
|
||||||
|
|
||||||
if (definition.input_type === "select") {
|
|
||||||
// Select input
|
|
||||||
const selectEl = document.createElement("select");
|
|
||||||
|
|
||||||
// Set data-id, name, id
|
|
||||||
selectEl.setAttribute("data-id", "value-input");
|
|
||||||
selectEl.setAttribute("name", valueInputEl.name);
|
|
||||||
selectEl.setAttribute("id", valueInputEl.id);
|
|
||||||
|
|
||||||
// Populate options
|
|
||||||
definition.options.forEach((option) => {
|
|
||||||
const optionEl = document.createElement("option");
|
|
||||||
optionEl.value = option[1];
|
|
||||||
optionEl.textContent = option[0];
|
|
||||||
selectEl.appendChild(optionEl);
|
|
||||||
});
|
|
||||||
|
|
||||||
valueInputEl.replaceWith(selectEl);
|
|
||||||
} else {
|
} else {
|
||||||
// Text input
|
const inputEl = this.#buildTextInput(definition, valueFieldEl);
|
||||||
const inputEl = document.createElement("input");
|
valueFieldEl.replaceWith(inputEl);
|
||||||
inputEl.setAttribute("data-id", "value-input");
|
|
||||||
inputEl.setAttribute("name", valueInputEl.name);
|
|
||||||
inputEl.setAttribute("id", valueInputEl.id);
|
|
||||||
inputEl.setAttribute("type", definition.input_type);
|
|
||||||
|
|
||||||
valueInputEl.replaceWith(inputEl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,32 +57,15 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleActionTypeChange(e) {
|
handleActionTypeChange(e) {
|
||||||
const definition = this.actionsRegistryValue.find((def) => {
|
const definition = this.#getActionExecutorDefinition(e.target.value);
|
||||||
return def.action_type === e.target.value;
|
const actionEl = this.#getEventActionEl(e.target);
|
||||||
});
|
const valueFieldEl = this.#getFieldEl(this.valueFieldTargets, actionEl);
|
||||||
|
|
||||||
const actionEl = this.actionTargets.find((t) => {
|
if (definition.type === "select") {
|
||||||
return t.contains(e.target);
|
const selectEl = this.#buildSelectInput(definition, valueFieldEl);
|
||||||
});
|
valueFieldEl.replaceWith(selectEl);
|
||||||
|
|
||||||
const valueInputEl = actionEl.querySelector("[data-id='value-input']");
|
|
||||||
|
|
||||||
if (definition.input_type === "select") {
|
|
||||||
const selectEl = document.createElement("select");
|
|
||||||
selectEl.setAttribute("data-id", "value-input");
|
|
||||||
selectEl.setAttribute("name", valueInputEl.name);
|
|
||||||
selectEl.setAttribute("id", valueInputEl.id);
|
|
||||||
|
|
||||||
definition.options.forEach((option) => {
|
|
||||||
const optionEl = document.createElement("option");
|
|
||||||
optionEl.value = option[1];
|
|
||||||
optionEl.textContent = option[0];
|
|
||||||
selectEl.appendChild(optionEl);
|
|
||||||
});
|
|
||||||
|
|
||||||
valueInputEl.replaceWith(selectEl);
|
|
||||||
} else {
|
} else {
|
||||||
valueInputEl.classList.add("hidden");
|
valueFieldEl.classList.add("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,16 +93,77 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#destroyRuleItem(itemEl) {
|
#updateOperatorsField(definition, conditionEl) {
|
||||||
const destroyInputEl = this.destroyInputTargets.find((el) => {
|
const operatorFieldEl = this.#getFieldEl(
|
||||||
return itemEl.contains(el);
|
this.operatorFieldTargets,
|
||||||
|
conditionEl,
|
||||||
|
);
|
||||||
|
|
||||||
|
operatorFieldEl.innerHTML = definition.operators
|
||||||
|
.map((operator) => {
|
||||||
|
return `<option value="${operator}">${operator}</option>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
#buildTextInput(definition, fieldEl) {
|
||||||
|
const inputEl = document.createElement("input");
|
||||||
|
inputEl.setAttribute("data-rule-target", "valueField");
|
||||||
|
inputEl.setAttribute("name", fieldEl.name);
|
||||||
|
inputEl.setAttribute("id", fieldEl.id);
|
||||||
|
inputEl.setAttribute("type", definition.type);
|
||||||
|
|
||||||
|
return inputEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buildSelectInput(definition, fieldEl) {
|
||||||
|
const selectEl = document.createElement("select");
|
||||||
|
selectEl.setAttribute("data-rule-target", "valueField");
|
||||||
|
selectEl.setAttribute("name", fieldEl.name);
|
||||||
|
selectEl.setAttribute("id", fieldEl.id);
|
||||||
|
|
||||||
|
definition.options.forEach((option) => {
|
||||||
|
const optionEl = document.createElement("option");
|
||||||
|
optionEl.textContent = option[0];
|
||||||
|
optionEl.value = option[1];
|
||||||
|
selectEl.appendChild(optionEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return selectEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
#destroyRuleItem(itemEl) {
|
||||||
|
const destroyFieldEl = this.#getFieldEl(this.destroyFieldTargets, itemEl);
|
||||||
|
|
||||||
itemEl.classList.add("hidden");
|
itemEl.classList.add("hidden");
|
||||||
destroyInputEl.value = true;
|
destroyFieldEl.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#uniqueKey() {
|
#uniqueKey() {
|
||||||
return `${Date.now()}_${Math.floor(Math.random() * 100000)}`;
|
return `${Date.now()}_${Math.floor(Math.random() * 100000)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#getConditionFilterDefinition(key) {
|
||||||
|
return this.registryValue.filters.find((filter) => {
|
||||||
|
return filter.key === key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#getActionExecutorDefinition(key) {
|
||||||
|
return this.registryValue.executors.find((executor) => {
|
||||||
|
return executor.key === key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#getEventConditionEl(childEl) {
|
||||||
|
return this.conditionTargets.find((t) => t.contains(childEl));
|
||||||
|
}
|
||||||
|
|
||||||
|
#getEventActionEl(childEl) {
|
||||||
|
return this.actionTargets.find((t) => t.contains(childEl));
|
||||||
|
}
|
||||||
|
|
||||||
|
#getFieldEl(targets, containerEl) {
|
||||||
|
return targets.find((t) => containerEl.contains(t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@ class Rule::ActionExecutor
|
||||||
{
|
{
|
||||||
type: type,
|
type: type,
|
||||||
key: key,
|
key: key,
|
||||||
label: label
|
label: label,
|
||||||
|
options: options
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
class Rule::ActionExecutor::SetTransactionCategory < Rule::ActionExecutor
|
class Rule::ActionExecutor::SetTransactionCategory < Rule::ActionExecutor
|
||||||
|
def type
|
||||||
|
"select"
|
||||||
|
end
|
||||||
|
|
||||||
def options
|
def options
|
||||||
family.categories.pluck(:name, :id)
|
family.categories.pluck(:name, :id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
class Rule::ActionExecutor::SetTransactionTags < Rule::ActionExecutor
|
class Rule::ActionExecutor::SetTransactionTags < Rule::ActionExecutor
|
||||||
|
def type
|
||||||
|
"select"
|
||||||
|
end
|
||||||
|
|
||||||
def options
|
def options
|
||||||
family.tags.pluck(:name, :id)
|
family.tags.pluck(:name, :id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,13 +47,19 @@ class Rule::ConditionFilter
|
||||||
{
|
{
|
||||||
type: type,
|
type: type,
|
||||||
key: key,
|
key: key,
|
||||||
label: label
|
label: label,
|
||||||
|
operators: operators,
|
||||||
|
options: options
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
attr_reader :rule
|
attr_reader :rule
|
||||||
|
|
||||||
|
def family
|
||||||
|
rule.family
|
||||||
|
end
|
||||||
|
|
||||||
def build_sanitized_where_condition(field, operator, value)
|
def build_sanitized_where_condition(field, operator, value)
|
||||||
sanitized_value = operator == "like" ? ActiveRecord::Base.sanitize_sql_like(value) : value
|
sanitized_value = operator == "like" ? ActiveRecord::Base.sanitize_sql_like(value) : value
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
<% rule = action.rule %>
|
<% rule = action.rule %>
|
||||||
|
|
||||||
<li data-rule-target="action">
|
<li data-rule-target="action">
|
||||||
<%= form.hidden_field :_destroy, value: false, data: { rule_target: "destroyInput" } %>
|
<%= form.hidden_field :_destroy, value: false, data: { rule_target: "destroyField" } %>
|
||||||
<%= form.select :action_type, rule.action_types, {}, data: { action: "rule#handleActionTypeChange" } %>
|
<%= form.select :action_type, rule.action_types, {}, data: { action: "rule#handleActionTypeChange" } %>
|
||||||
<span>to</span>
|
<span>to</span>
|
||||||
<%= form.select :value, action.options, {}, data: { id: "value-input" } %>
|
<%= form.select :value, action.options, {}, data: { rule_target: "valueField" } %>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="rule#removeAction"
|
data-action="rule#removeAction"
|
||||||
data-rule-destroy-param="<%= action.persisted? %>">
|
data-rule-destroy-param="<%= action.persisted? %>">
|
||||||
|
|
|
@ -4,10 +4,16 @@
|
||||||
<% rule = condition.rule %>
|
<% rule = condition.rule %>
|
||||||
|
|
||||||
<li data-rule-target="condition">
|
<li data-rule-target="condition">
|
||||||
<%= form.hidden_field :_destroy, value: false, data: { rule_target: "destroyInput" } %>
|
<%= form.hidden_field :_destroy, value: false, data: { rule_target: "destroyField" } %>
|
||||||
<%= form.select :condition_type, rule.condition_types, {}, data: { action: "rule#handleConditionTypeChange" } %>
|
<%= form.select :condition_type, rule.condition_types, {}, data: { action: "rule#handleConditionTypeChange" } %>
|
||||||
<%= form.select :operator, condition.operators, {}, data: { id: "operator-select" } %>
|
<%= form.select :operator, condition.operators, {}, data: { rule_target: "operatorField" } %>
|
||||||
<%= form.text_field :value, data: { id: "value-input" } %>
|
|
||||||
|
<% if condition.filter.type == "select" %>
|
||||||
|
<%= form.select :value, condition.options, {}, data: { rule_target: "valueField" } %>
|
||||||
|
<% else %>
|
||||||
|
<%= form.text_field :value, data: { rule_target: "valueField" } %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="rule#removeCondition"
|
data-action="rule#removeCondition"
|
||||||
data-rule-destroy-param="<%= condition.persisted? %>">
|
data-rule-destroy-param="<%= condition.persisted? %>">
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<%= link_to "Back to rules", rules_path %>
|
||||||
<h2>Edit <%= @rule.resource_type %> rule</h2>
|
<h2>Edit <%= @rule.resource_type %> rule</h2>
|
||||||
|
|
||||||
<%= render "rules/form", rule: @rule %>
|
<%= render "rules/form", rule: @rule %>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<% content_for :page_title, "Rules" %>
|
<% content_for :page_title, "Rules" %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<%= link_to "New rule", new_rule_path %>
|
||||||
<ul>
|
<ul>
|
||||||
<% @rules.each do |rule| %>
|
<% @rules.each do |rule| %>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<%= link_to "Back to rules", rules_path %>
|
||||||
<h2>New <%= @rule.resource_type %> rule</h2>
|
<h2>New <%= @rule.resource_type %> rule</h2>
|
||||||
|
|
||||||
<%= render "rules/form", rule: @rule %>
|
<%= render "rules/form", rule: @rule %>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue