Workflow status creation (extra forms)

This commit is contained in:
2026-04-14 01:26:28 +03:00
parent 450b7fb1d0
commit 4040fff780
6 changed files with 146 additions and 24 deletions
@@ -0,0 +1,15 @@
.workflow-statuses-batch-update-fieldsets {
display: grid;
grid-template-columns: repeat(3, auto) min-content;
gap: 0.5em;
> .fieldset {
display: grid;
grid-column: 1 / -1;
grid-template-columns: subgrid;
.buttons {
align-items: flex-end;
}
}
}
@@ -17,7 +17,7 @@ module ProjectAdmin
end end
@form = ProjectAdmin::Workflows::Statuses::BatchUpdate.new(form_params) @form = ProjectAdmin::Workflows::Statuses::BatchUpdate.new(form_params)
if @form.perform(@workflow) if @form.call(@workflow)
redirect_to project_admin_workflow_path(@project, @workflow) redirect_to project_admin_workflow_path(@project, @workflow)
else else
render :edit render :edit
@@ -0,0 +1,74 @@
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = ['template', 'container', 'subform']
static values = {
fieldName: String,
fieldTemplate: String,
indexTemplate: String,
count: Number
}
connect() {
this.extraFieldsCount = 0
}
addForm() {
this.countValue++;
this.extraFieldsCount++;
const content = document.importNode(this.templateTarget.content, true)
for (let element of content.querySelectorAll('label, input, select, textarea')) {
this._replaceAttr(element, 'name')
this._replaceAttr(element, 'id')
this._replaceAttr(element, 'for')
}
let idInput = content.querySelector('input[name$="[id]"]')
if (idInput) {
idInput.value = `_${this.extraFieldsCount}`
}
this._subformsContainer.insertAdjacentElement('beforeend', content.children[0])
}
toggleSubform(event) {
// TODO: maybe extract into a separate controller
let target = event.currentTarget
let subform = this._findSubform(target)
let disabled = subform.toggleAttribute('data-disabled')
for (let input of subform.querySelectorAll('input:not([type="hidden"]), select, textarea')) {
input.toggleAttribute('disabled', disabled)
}
let destroyInput = subform.querySelector('input[name$="[_destroy]"]')
if(destroyInput) { destroyInput.value = disabled ? 'true' : '' }
if(target.tagName == 'BUTTON') {
target.setAttribute('aria-pressed', disabled ? 'true' : 'false')
}
}
deleteSubform(event) {
let subform = this._findSubform(event.currentTarget)
subform.remove()
}
get _subformsContainer() {
if(this.hasContainerTarget) { return this.containerTarget }
return this.element
}
_findSubform(element) {
for (let subform of this.subformTargets) {
if(subform.contains(element)) { return subform }
}
}
_replaceAttr(element, attr) {
let value = element.getAttribute(attr)
if(value == null) return;
value = value.replace(this.fieldTemplateValue, this.fieldNameValue).replace(this.indexTemplateValue, this.countValue)
element.setAttribute(attr, value)
}
}
@@ -28,27 +28,45 @@ module ProjectAdmin
@task_statuses = Array(attributes).map { |e| TaskStatus.new(e) } @task_statuses = Array(attributes).map { |e| TaskStatus.new(e) }
end end
def perform(workflow) def call(workflow)
@workflow = workflow @workflow = workflow
task_status_models = @workflow.task_statuses.index_by(&:id) task_status_models = @workflow.task_statuses.index_by(&:id)
@workflow.transaction(requires_new: true) do @workflow.transaction(requires_new: true) do
task_statuses.each do |ts| task_statuses.each do |ts|
model = task_status_models.fetch(ts.id.to_i) if ts.id.start_with?('_')
if ts._destroy create_model!(ts)
model.destroy!
else else
model.update!( model = task_status_models.fetch(Integer(ts.id))
name: ts.name, if ts._destroy
icon: ts.icon, model.destroy!
color: ts.color else
) update_model!(model, ts)
end
end end
end end
end end
true true
end end
private
def update_model!(model, form)
model.update!(
name: form.name,
icon: form.icon,
color: form.color
)
end
def create_model!(form)
@workflow.task_statuses.create!(
name: form.name,
icon: form.icon,
color: form.color
)
end
end end
end end
end end
@@ -0,0 +1,17 @@
- with_destroy ||= false
div.fieldset data-dynamic-forms-target="subform"
= ff.hidden_field :id
- if with_destroy
= ff.hidden_field :_destroy
.field
= ff.label :name
= ff.text_field :name
.field
= ff.label :color
= ff.select :color, TaskStatus.colors
.field
= ff.label :icon
= ff.select :icon, TaskStatus.icons
.buttons
= button_tag 'Destroy', type: :button, class: 'danger', data: { action: (with_destroy ? 'dynamic-forms#toggleSubform' : 'dynamic-forms#deleteSubform') }
@@ -1,16 +1,14 @@
= form_with model: @form, scope: 'workflow', url: project_admin_workflow_statuses_path(@project, @workflow), method: :put do |f| = form_with model: @form, scope: 'workflow', url: project_admin_workflow_statuses_path(@project, @workflow), method: :put, data: {controller: 'dynamic-forms', 'dynamic-forms-field-name-value': 'task_statuses_attributes', 'dynamic-forms-field-template-value': '_extra_form', 'dynamic-forms-index-template-value': '__index__', 'dynamic-forms-count-value': @form.task_statuses.count} do |f|
= f.fields_for :task_statuses, include_id: false do |tsf| section.workflow-statuses-batch-update-fieldsets data-dynamic-forms-target="container"
fieldset = f.fields_for :task_statuses, include_id: false do |tsf|
= tsf.hidden_field :id = render 'batch_update_fieldset', ff: tsf, with_destroy: true
= tsf.hidden_field :_destroy
.field .buttons
= tsf.label :name button.success type="button" data-action="dynamic-forms#addForm" Add status
= tsf.text_field :name
.field = f.fields_for :'_extra_form', index: '__index__' do |exf|
= tsf.label :color template data-dynamic-forms-target="template"
= tsf.select :color, TaskStatus.colors = render 'batch_update_fieldset', ff: exf
.field
= tsf.label :icon
= tsf.select :icon, TaskStatus.icons
.submit .submit
= f.submit = f.submit