From 450b7fb1d017a37b73470ea24c3d3616b3dac165 Mon Sep 17 00:00:00 2001 From: Artemiy Solopov Date: Mon, 13 Apr 2026 02:22:52 +0300 Subject: [PATCH] Workflow statuses editing --- .../workflows/statuses_controller.rb | 34 ++++++++++++ .../workflows/statuses_helper.rb | 2 + app/helpers/tasks_helper.rb | 8 +++ app/models/task_status.rb | 1 - .../workflows/statuses/batch_update.rb | 55 +++++++++++++++++++ .../tasks/statuses/selector_view_model.rb | 20 +++---- .../project_admin/workflows/show.html.slim | 2 +- .../workflows/statuses/edit.html.slim | 16 ++++++ app/views/tasks/_status_selector.html.slim | 4 +- config/routes.rb | 8 ++- 10 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 app/controllers/project_admin/workflows/statuses_controller.rb create mode 100644 app/helpers/project_admin/workflows/statuses_helper.rb create mode 100644 app/services/project_admin/workflows/statuses/batch_update.rb create mode 100644 app/views/project_admin/workflows/statuses/edit.html.slim diff --git a/app/controllers/project_admin/workflows/statuses_controller.rb b/app/controllers/project_admin/workflows/statuses_controller.rb new file mode 100644 index 0000000..c329d92 --- /dev/null +++ b/app/controllers/project_admin/workflows/statuses_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ProjectAdmin + module Workflows + class StatusesController < ApplicationController + before_action :fetch_workflow + + def edit + @form = ProjectAdmin::Workflows::Statuses::BatchUpdate.from_model(@workflow) + end + + def batch_update + form_params = params.expect(workflow: { task_statuses_attributes: [%i[id _destroy name color icon]] }) + + if form_params[:task_statuses_attributes].respond_to?(:keys) + form_params[:task_statuses_attributes] = form_params[:task_statuses_attributes].values + end + + @form = ProjectAdmin::Workflows::Statuses::BatchUpdate.new(form_params) + if @form.perform(@workflow) + redirect_to project_admin_workflow_path(@project, @workflow) + else + render :edit + end + end + + private + + def fetch_workflow + @workflow = @project.workflows.find(params[:workflow_id]) + end + end + end +end diff --git a/app/helpers/project_admin/workflows/statuses_helper.rb b/app/helpers/project_admin/workflows/statuses_helper.rb new file mode 100644 index 0000000..fde2422 --- /dev/null +++ b/app/helpers/project_admin/workflows/statuses_helper.rb @@ -0,0 +1,2 @@ +module ProjectAdmin::Workflows::StatusesHelper +end diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index 204ed67..f9feacf 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -12,4 +12,12 @@ module TasksHelper def task_status_selector(task, with_form: false) render Tasks::Statuses::SelectorViewModel.new(task, with_form:) end + + # TODO: move into another helper? + def task_status_badge(status) + content_tag( + :span, mask_icon(Tasks::Statuses::SelectorViewModel.icon(status)) + status.name, + class: ['badge', 'task-status', status.color] + ) + end end diff --git a/app/models/task_status.rb b/app/models/task_status.rb index 847d9d3..6f516c9 100644 --- a/app/models/task_status.rb +++ b/app/models/task_status.rb @@ -8,7 +8,6 @@ class TaskStatus < ApplicationRecord enum :color, %w[blue gray yellow green purple pink].index_by(&:itself), default: 'gray', scopes: false validates :name, presence: true, uniqueness: { scope: :workflow } - validates :category, presence: true scope :default_order, -> { order(:position, :name) } end diff --git a/app/services/project_admin/workflows/statuses/batch_update.rb b/app/services/project_admin/workflows/statuses/batch_update.rb new file mode 100644 index 0000000..c183228 --- /dev/null +++ b/app/services/project_admin/workflows/statuses/batch_update.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module ProjectAdmin + module Workflows + module Statuses + class BatchUpdate < ApplicationService + class TaskStatus + include ActiveModel::Model + include ActiveModel::Attributes + + attribute :id, :string + attribute :_destroy, :boolean + attribute :name + attribute :color + attribute :icon + end + + attr_accessor :task_statuses + attr_reader :workflow + + def self.from_model(workflow) + new(task_statuses_attributes: workflow.task_statuses.map do |ts| + ts.attributes.slice(*TaskStatus.attribute_names) + end) + end + + def task_statuses_attributes=(attributes) + @task_statuses = Array(attributes).map { |e| TaskStatus.new(e) } + end + + def perform(workflow) + @workflow = workflow + task_status_models = @workflow.task_statuses.index_by(&:id) + + @workflow.transaction(requires_new: true) do + task_statuses.each do |ts| + model = task_status_models.fetch(ts.id.to_i) + if ts._destroy + model.destroy! + else + model.update!( + name: ts.name, + icon: ts.icon, + color: ts.color + ) + end + end + end + + true + end + end + end + end +end diff --git a/app/view_models/tasks/statuses/selector_view_model.rb b/app/view_models/tasks/statuses/selector_view_model.rb index 91824e1..5a3c11c 100644 --- a/app/view_models/tasks/statuses/selector_view_model.rb +++ b/app/view_models/tasks/statuses/selector_view_model.rb @@ -13,6 +13,12 @@ module Tasks tool: 'tool_line' }.freeze + def self.icon(status) + ICONS.fetch(status.icon.to_sym) + end + + delegate :icon, to: :'self.class' + def initialize(task, with_form: false) @task = task @with_form = with_form @@ -27,8 +33,7 @@ module Tasks view_context.render( partial: 'tasks/status_selector', locals: { task: @task, id: dom_id, with_form: @with_form, - workflow_task_statuses:, - task_status_badge: ->(status) { task_status_badge(status, view_context) } } + workflow_task_statuses: } ) end @@ -37,17 +42,6 @@ module Tasks def workflow_task_statuses @task.workflow.task_statuses.sort_by { |e| [e.position, e.name] } end - - def task_status_badge(status, view_context) - view_context.content_tag( - :span, view_context.mask_icon(icon(status)) + status.name, - class: ['badge', 'task-status', status.color] - ) - end - - def icon(status) - ICONS.fetch(status.icon.to_sym) - end end end end diff --git a/app/views/project_admin/workflows/show.html.slim b/app/views/project_admin/workflows/show.html.slim index 43e2976..9c165ac 100644 --- a/app/views/project_admin/workflows/show.html.slim +++ b/app/views/project_admin/workflows/show.html.slim @@ -5,4 +5,4 @@ h1 ul - @workflow.task_statuses.each do |status| - li= status.name + li= task_status_badge(status) diff --git a/app/views/project_admin/workflows/statuses/edit.html.slim b/app/views/project_admin/workflows/statuses/edit.html.slim new file mode 100644 index 0000000..1bcfdfe --- /dev/null +++ b/app/views/project_admin/workflows/statuses/edit.html.slim @@ -0,0 +1,16 @@ += form_with model: @form, scope: 'workflow', url: project_admin_workflow_statuses_path(@project, @workflow), method: :put do |f| + = f.fields_for :task_statuses, include_id: false do |tsf| + fieldset + = tsf.hidden_field :id + = tsf.hidden_field :_destroy + .field + = tsf.label :name + = tsf.text_field :name + .field + = tsf.label :color + = tsf.select :color, TaskStatus.colors + .field + = tsf.label :icon + = tsf.select :icon, TaskStatus.icons + .submit + = f.submit diff --git a/app/views/tasks/_status_selector.html.slim b/app/views/tasks/_status_selector.html.slim index 957c2d8..df9f0ae 100644 --- a/app/views/tasks/_status_selector.html.slim +++ b/app/views/tasks/_status_selector.html.slim @@ -1,9 +1,9 @@ details.dropdown.task-status-selector id=id data-controller="tasks--status-selector" - summary= task_status_badge[task.status] + summary= task_status_badge(task.status) ul - workflow_task_statuses.each do |status| li - a href="#" data-status-id="#{status.id}" data-action="tasks--status-selector#changeStatus:prevent" = task_status_badge[status] + a href="#" data-status-id="#{status.id}" data-action="tasks--status-selector#changeStatus:prevent" = task_status_badge(status) - if with_form = form_with model: Tasks::ChangeStatus.new, url: change_status_task_path(task), method: :patch, data: {'tasks--status-selector-target': 'form', action: 'turbo:submit-end->tasks--status-selector#finalize'} do |f| = f.hidden_field :status_id, data: {'tasks--status-selector-target': 'statusField'} diff --git a/config/routes.rb b/config/routes.rb index b4550d2..6f179b1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -21,9 +21,11 @@ Rails.application.routes.draw do resources :projects do namespace :project_admin, as: 'admin' do resources :workflows do - namespace :statuses do - get '/', action: :index - patch '/', action: :batch_update + scope module: :workflows do + resources :statuses, only: %i[index] do + get :edit, on: :collection + put '/', action: :batch_update, on: :collection + end end end end