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 ]
|
||||
|
||||
def index
|
||||
@rules = Current.family.rules
|
||||
@rules = Current.family.rules.order(created_at: :desc)
|
||||
render layout: "settings"
|
||||
end
|
||||
|
||||
|
|
|
@ -3,8 +3,7 @@ import { Controller } from "@hotwired/stimulus";
|
|||
// Connects to data-controller="rule"
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
conditionsRegistry: Array,
|
||||
actionsRegistry: Array,
|
||||
registry: Object,
|
||||
};
|
||||
|
||||
static targets = [
|
||||
|
@ -14,12 +13,13 @@ export default class extends Controller {
|
|||
"condition",
|
||||
"actionsList",
|
||||
"action",
|
||||
"destroyInput",
|
||||
"destroyField",
|
||||
"operatorField",
|
||||
"valueField",
|
||||
];
|
||||
|
||||
initialize() {
|
||||
console.log(this.conditionsRegistryValue);
|
||||
console.log(this.actionsRegistryValue);
|
||||
console.log(this.registryValue);
|
||||
}
|
||||
|
||||
addCondition() {
|
||||
|
@ -32,53 +32,18 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
handleConditionTypeChange(e) {
|
||||
const definition = this.conditionsRegistryValue.find((def) => {
|
||||
return def.condition_type === e.target.value;
|
||||
});
|
||||
const definition = this.#getConditionFilterDefinition(e.target.value);
|
||||
const conditionEl = this.#getEventConditionEl(e.target);
|
||||
const valueFieldEl = this.#getFieldEl(this.valueFieldTargets, conditionEl);
|
||||
|
||||
const conditionEl = this.conditionTargets.find((t) => {
|
||||
return t.contains(e.target);
|
||||
});
|
||||
this.#updateOperatorsField(definition, conditionEl);
|
||||
|
||||
const operatorSelectEl = conditionEl.querySelector(
|
||||
"select[data-id='operator-select']",
|
||||
);
|
||||
|
||||
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);
|
||||
if (definition.type === "select") {
|
||||
const selectEl = this.#buildSelectInput(definition, valueFieldEl);
|
||||
valueFieldEl.replaceWith(selectEl);
|
||||
} else {
|
||||
// Text input
|
||||
const inputEl = document.createElement("input");
|
||||
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);
|
||||
const inputEl = this.#buildTextInput(definition, valueFieldEl);
|
||||
valueFieldEl.replaceWith(inputEl);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,32 +57,15 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
handleActionTypeChange(e) {
|
||||
const definition = this.actionsRegistryValue.find((def) => {
|
||||
return def.action_type === e.target.value;
|
||||
});
|
||||
const definition = this.#getActionExecutorDefinition(e.target.value);
|
||||
const actionEl = this.#getEventActionEl(e.target);
|
||||
const valueFieldEl = this.#getFieldEl(this.valueFieldTargets, actionEl);
|
||||
|
||||
const actionEl = this.actionTargets.find((t) => {
|
||||
return t.contains(e.target);
|
||||
});
|
||||
|
||||
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);
|
||||
if (definition.type === "select") {
|
||||
const selectEl = this.#buildSelectInput(definition, valueFieldEl);
|
||||
valueFieldEl.replaceWith(selectEl);
|
||||
} else {
|
||||
valueInputEl.classList.add("hidden");
|
||||
valueFieldEl.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,16 +93,77 @@ export default class extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
#destroyRuleItem(itemEl) {
|
||||
const destroyInputEl = this.destroyInputTargets.find((el) => {
|
||||
return itemEl.contains(el);
|
||||
#updateOperatorsField(definition, conditionEl) {
|
||||
const operatorFieldEl = this.#getFieldEl(
|
||||
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");
|
||||
destroyInputEl.value = true;
|
||||
destroyFieldEl.value = true;
|
||||
}
|
||||
|
||||
#uniqueKey() {
|
||||
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,
|
||||
key: key,
|
||||
label: label
|
||||
label: label,
|
||||
options: options
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
class Rule::ActionExecutor::SetTransactionCategory < Rule::ActionExecutor
|
||||
def type
|
||||
"select"
|
||||
end
|
||||
|
||||
def options
|
||||
family.categories.pluck(:name, :id)
|
||||
end
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
class Rule::ActionExecutor::SetTransactionTags < Rule::ActionExecutor
|
||||
def type
|
||||
"select"
|
||||
end
|
||||
|
||||
def options
|
||||
family.tags.pluck(:name, :id)
|
||||
end
|
||||
|
|
|
@ -47,13 +47,19 @@ class Rule::ConditionFilter
|
|||
{
|
||||
type: type,
|
||||
key: key,
|
||||
label: label
|
||||
label: label,
|
||||
operators: operators,
|
||||
options: options
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :rule
|
||||
|
||||
def family
|
||||
rule.family
|
||||
end
|
||||
|
||||
def build_sanitized_where_condition(field, operator, value)
|
||||
sanitized_value = operator == "like" ? ActiveRecord::Base.sanitize_sql_like(value) : value
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
<% rule = action.rule %>
|
||||
|
||||
<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" } %>
|
||||
<span>to</span>
|
||||
<%= form.select :value, action.options, {}, data: { id: "value-input" } %>
|
||||
<%= form.select :value, action.options, {}, data: { rule_target: "valueField" } %>
|
||||
<button type="button"
|
||||
data-action="rule#removeAction"
|
||||
data-rule-destroy-param="<%= action.persisted? %>">
|
||||
|
|
|
@ -4,10 +4,16 @@
|
|||
<% rule = condition.rule %>
|
||||
|
||||
<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 :operator, condition.operators, {}, data: { id: "operator-select" } %>
|
||||
<%= form.text_field :value, data: { id: "value-input" } %>
|
||||
<%= form.select :operator, condition.operators, {}, data: { rule_target: "operatorField" } %>
|
||||
|
||||
<% 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"
|
||||
data-action="rule#removeCondition"
|
||||
data-rule-destroy-param="<%= condition.persisted? %>">
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%= link_to "Back to rules", rules_path %>
|
||||
<h2>Edit <%= @rule.resource_type %> rule</h2>
|
||||
|
||||
<%= render "rules/form", rule: @rule %>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<% content_for :page_title, "Rules" %>
|
||||
|
||||
<div>
|
||||
<%= link_to "New rule", new_rule_path %>
|
||||
<ul>
|
||||
<% @rules.each do |rule| %>
|
||||
<li>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%= link_to "Back to rules", rules_path %>
|
||||
<h2>New <%= @rule.resource_type %> rule</h2>
|
||||
|
||||
<%= render "rules/form", rule: @rule %>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue