diff --git a/app/components/avo/views/resource_edit_component.html.erb b/app/components/avo/views/resource_edit_component.html.erb index 2836f1d241..0d64ebcded 100644 --- a/app/components/avo/views/resource_edit_component.html.erb +++ b/app/components/avo/views/resource_edit_component.html.erb @@ -9,7 +9,7 @@ **@resource.stimulus_data_attributes } do %> <%= render_cards_component %> - <%= form_with model: @resource.record, + <%= form_with model: model, scope: @resource.form_scope, url: form_url, method: form_method, @@ -23,6 +23,13 @@ }, multipart: true do |form| %> <%= render Avo::ReferrerParamsComponent.new back_path: back_path %> + + <% if @prefilled_fields.present? %> + <% @prefilled_fields.each do |field, value| %> + <%= hidden_field_tag "prefilled[#{field}]", value %> + <% end %> + <% end %> + <%= content_tag :div, class: "space-y-12" do %> <% @resource.get_items.each_with_index do |item, index| %> <%= render Avo::Items::SwitcherComponent.new( diff --git a/app/components/avo/views/resource_edit_component.rb b/app/components/avo/views/resource_edit_component.rb index 9a5c4baccf..cf5a7aab5a 100644 --- a/app/components/avo/views/resource_edit_component.rb +++ b/app/components/avo/views/resource_edit_component.rb @@ -9,6 +9,14 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent prop :view, default: Avo::ViewInquirer.new(:edit).freeze prop :display_breadcrumbs, default: true, reader: :public + attr_reader :query + + def initialize(resource:, query: nil, prefilled_fields: nil, **args) + @query = query + @prefilled_fields = prefilled_fields + super(resource: resource, **args) + end + def after_initialize @display_breadcrumbs = @reflection.blank? && display_breadcrumbs end @@ -18,18 +26,22 @@ def title end def back_path - # The `return_to` param takes precedence over anything else. - return params[:return_to] if params[:return_to].present? - - return if via_belongs_to? - return resource_view_path if via_resource? - return resources_path if via_index? - - if is_edit? && Avo.configuration.resource_default_view.show? # via resource show or edit page - return helpers.resource_path(record: @resource.record, resource: @resource, **keep_referrer_params) + if params[:controller] == "avo/bulk_update" + helpers.resources_path(resource: @resource) + elsif params[:return_to].present? + # The `return_to` param takes precedence over anything else. + params[:return_to] + elsif via_belongs_to? + nil + elsif via_resource? + resource_view_path + elsif via_index? + resources_path + elsif is_edit? && Avo.configuration.resource_default_view.show? # via resource show or edit page + helpers.resource_path(record: @resource.record, resource: @resource, **keep_referrer_params) + else + resources_path end - - resources_path end def resources_path @@ -76,13 +88,19 @@ def is_edit? end def form_method - return :put if is_edit? + return :put if is_edit? && params[:controller] != "avo/bulk_update" :post end + def model + @resource.record + end + def form_url - if is_edit? + if params[:controller] == "avo/bulk_update" + helpers.handle_bulk_update_path(resource_name: @resource.name, query: @query) + elsif is_edit? helpers.resource_path( record: @resource.record, resource: @resource diff --git a/app/components/avo/views/resource_index_component.html.erb b/app/components/avo/views/resource_index_component.html.erb index 9cbe4aa5dc..505de0cb6c 100644 --- a/app/components/avo/views/resource_index_component.html.erb +++ b/app/components/avo/views/resource_index_component.html.erb @@ -49,6 +49,8 @@ <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters, parent_record: @parent_record %> <%= render partial: "avo/partials/view_toggle_button", locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: @turbo_frame } %> + + <%= render_bulk_update_button %> <% if has_dynamic_filters? %> diff --git a/app/components/avo/views/resource_index_component.rb b/app/components/avo/views/resource_index_component.rb index 39de6aa044..559dbcf45c 100644 --- a/app/components/avo/views/resource_index_component.rb +++ b/app/components/avo/views/resource_index_component.rb @@ -3,6 +3,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent include Avo::ResourcesHelper include Avo::ApplicationHelper + include Avo::Concerns::ChecksShowAuthorization prop :resource prop :resources @@ -33,6 +34,20 @@ def view_type @index_params[:view_type] end + def bulk_edit_path + # Add the `view` param to let Avo know where to redirect back when the user clicks the `Cancel` button. + args = {via_view: "index"} + + if @parent_record.present? + args = { + via_resource_class: parent_resource.class.to_s, + via_record_id: @parent_record.to_param + } + end + + helpers.edit_bulk_update_path(resource: @resource, **args) + end + def available_view_types @index_params[:available_view_types] end @@ -154,6 +169,16 @@ def render_dynamic_filters_button end end + def render_bulk_update_button + a_link helpers.edit_bulk_update_path(resource_name: @resource.name), + style: :primary, + color: :primary, + icon: "avo/edit", + form_class: "flex flex-col sm:flex-row sm:inline-flex" do + I18n.t("avo.bulk_update") + end + end + def scopes_list Avo::Advanced::Scopes::ListComponent.new( scopes: @scopes, diff --git a/app/controllers/avo/base_controller.rb b/app/controllers/avo/base_controller.rb index 94224ee380..7a07848e82 100644 --- a/app/controllers/avo/base_controller.rb +++ b/app/controllers/avo/base_controller.rb @@ -7,7 +7,7 @@ class BaseController < ApplicationController before_action :set_resource_name before_action :set_resource before_action :set_applied_filters, only: :index - before_action :set_record, only: [:show, :edit, :destroy, :update, :preview] + before_action :set_record, only: [:show, :edit, :destroy, :update, :preview], if: -> { controller_name != "bulk_update" } before_action :set_record_to_fill, only: [:new, :edit, :create, :update] before_action :detect_fields before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update] @@ -416,8 +416,7 @@ def filters_to_be_applied end def set_edit_title_and_breadcrumbs - @resource = @resource.hydrate(record: @record, view: Avo::ViewInquirer.new(:edit), user: _current_user) - @page_title = @resource.default_panel_name.to_s + set_resource_and_page_title last_crumb_args = {} # If we're accessing this resource via another resource add the parent to the breadcrumbs. @@ -438,8 +437,23 @@ def set_edit_title_and_breadcrumbs add_breadcrumb @resource.plural_name.humanize, resources_path(resource: @resource) end - add_breadcrumb @resource.record_title, resource_path(record: @resource.record, resource: @resource, **last_crumb_args) - add_breadcrumb t("avo.edit").humanize + help_add_breadcrumb(last_crumb_args) + end + + def set_resource_and_page_title + if params[:controller] != "avo/bulk_update" + @resource = @resource.hydrate(record: @record, view: Avo::ViewInquirer.new(:edit), user: _current_user) + @page_title = @resource.default_panel_name.to_s + end + end + + def help_add_breadcrumb(last_crumb_args) + if params[:controller] != "avo/bulk_update" + add_breadcrumb @resource.record_title, resource_path(record: @resource.record, resource: @resource, **last_crumb_args) if params[:controller] != "avo/bulk_update" + add_breadcrumb t("avo.edit").humanize + else + add_breadcrumb t("avo.bulk_edit") + end end def create_success_action diff --git a/app/controllers/avo/bulk_update_controller.rb b/app/controllers/avo/bulk_update_controller.rb new file mode 100644 index 0000000000..3dbcced78a --- /dev/null +++ b/app/controllers/avo/bulk_update_controller.rb @@ -0,0 +1,131 @@ +module Avo + class BulkUpdateController < ResourcesController + before_action :set_query, only: [:edit, :handle] + before_action :set_fields, only: [:edit, :handle] + + def edit + @prefilled_fields = prefill_fields(@query, @fields) + @record = @resource.model_class.new(@prefilled_fields.transform_values { |v| v.nil? ? nil : v }) + + @resource.record = @record + render Avo::Views::ResourceEditComponent.new( + resource: @resource, + query: @query, + prefilled_fields: @prefilled_fields + ) + end + + def handle + saved = save_records + + if saved + flash[:notice] = t("avo.bulk_update_success") + else + flash[:error] = t("avo.bulk_update_failure") + end + + redirect_to after_bulk_update_path + end + + private + + def update_records + params = params_to_apply + + @query.each do |record| + @resource.fill_record(record, params) + end + end + + def save_records + update_records + + all_saved = true + + ActiveRecord::Base.transaction do + @query.each do |record| + @record = record + save_record + end + rescue ActiveRecord::RecordInvalid => e + all_saved = false + puts "Failed to save #{record.id}: #{e.message}" + raise ActiveRecord::Rollback + end + + all_saved + end + + def params_to_apply + prefilled_params = params[:prefilled] || {} + current_params = current_resource_params + progress_fields = progress_bar_fields + + current_params.reject do |key, value| + key_sym = key.to_sym + + prefilled_value = prefilled_params[key_sym] + + progress_field_with_default?(progress_fields, key_sym, prefilled_value, value) || prefilled_value.to_s == value.to_s + end + end + + def current_resource_params + resource_key = @resource_name.downcase.to_sym + params[resource_key] || {} + end + + def progress_bar_fields + @resource.get_field_definitions + .select { |field| field.is_a?(Avo::Fields::ProgressBarField) } + .map(&:id) + .map(&:to_sym) + end + + def progress_field_with_default?(progress_fields, key_sym, prefilled_value, value) + progress_fields.include?(key_sym) && prefilled_value.nil? && value.to_s == "50" + end + + def prefill_fields(records, fields) + fields.each_key.with_object({}) do |field_name, prefilled| + values = records.map { |record| record.public_send(field_name) } + values.uniq! + prefilled[field_name] = values.first if values.size == 1 + end + end + + def set_query + @query = if params[:query].present? + @resource.find_record(params[:query], params: params) + else + find_records_by_resource_ids + end + end + + def find_records_by_resource_ids + resource_ids = params[:fields]&.dig(:avo_resource_ids)&.split(",") || [] + decrypted_query || (resource_ids.any? ? @resource.find_record(resource_ids, params: params) : []) + end + + def set_fields + if @query.blank? + flash[:error] = I18n.t("avo.bulk_update_no_records") + redirect_to after_bulk_update_path + else + @fields = @query.first.attributes.keys.index_with { nil } + end + end + + def decrypted_query + encrypted_query = params[:fields]&.dig(:avo_selected_query) || params[:query] + + return if encrypted_query.blank? + + Avo::Services::EncryptionService.decrypt(message: encrypted_query, purpose: :select_all, serializer: Marshal) + end + + def after_bulk_update_path + resources_path(resource: @resource) + end + end +end diff --git a/app/helpers/avo/url_helpers.rb b/app/helpers/avo/url_helpers.rb index 97e47b30f1..42e5e1709d 100644 --- a/app/helpers/avo/url_helpers.rb +++ b/app/helpers/avo/url_helpers.rb @@ -44,6 +44,14 @@ def edit_resource_path(resource:, record: nil, resource_id: nil, **args) avo.send :"edit_resources_#{resource.singular_route_key}_path", record || resource_id, **args end + def edit_bulk_update_path(resource_name:, id:, **args) + avo.send :edit_bulk_update_path, resource_name, id, **args + end + + def handle_bulk_update_path(resource_name:, query:, **args) + avo.send :handle_bulk_update_path, resource_name, query, **args + end + def resource_attach_path(resource, record_id, related_name, related_id = nil) helpers.avo.resources_associations_new_path(resource.singular_route_key, record_id, related_name) end diff --git a/app/javascript/js/controllers/item_select_all_controller.js b/app/javascript/js/controllers/item_select_all_controller.js index 56583daf7c..509390632f 100644 --- a/app/javascript/js/controllers/item_select_all_controller.js +++ b/app/javascript/js/controllers/item_select_all_controller.js @@ -71,6 +71,7 @@ export default class extends Controller { } this.updateLinks('resourceIds') + this.updateBulkEditLink('resourceIds') } selectAll(event) { @@ -82,22 +83,32 @@ export default class extends Controller { if (this.selectedAllValue) { this.updateLinks('selectedQuery') + this.updateBulkEditLink('selectedQuery') } else { this.updateLinks('resourceIds') + this.updateBulkEditLink('resourceIds') } } updateLinks(param) { - let resourceIds = '' - let selectedQuery = '' + this.updateActionLinks(param, '[data-target="actions-list"] > a', { + resourceIdsKey: 'fields[avo_resource_ids]', + selectedQueryKey: 'fields[avo_index_query]', + selectedAllKey: 'fields[avo_selected_all]', + }) + } - if (param === 'resourceIds') { - resourceIds = JSON.parse(this.element.dataset.selectedResources).join(',') - } else if (param === 'selectedQuery') { - selectedQuery = this.element.dataset.itemSelectAllSelectedAllQueryValue - } + updateBulkEditLink(param) { + this.updateActionLinks(param, 'a[href*="/admin/bulk_update/edit"]', { + resourceIdsKey: 'fields[avo_resource_ids]', + selectedQueryKey: 'fields[avo_selected_query]', + }) + } - document.querySelectorAll('[data-target="actions-list"] > a').forEach((link) => { + updateActionLinks(param, selector, keys) { + const params = this.setLinkParams(keys) + + document.querySelectorAll(selector).forEach((link) => { try { const url = new URL(link.href) @@ -105,21 +116,35 @@ export default class extends Controller { .filter((key) => key.startsWith('fields[')) .forEach((key) => url.searchParams.delete(key)) - if (param === 'resourceIds') { - url.searchParams.set('fields[avo_resource_ids]', resourceIds) - url.searchParams.set('fields[avo_selected_all]', 'false') - } else if (param === 'selectedQuery') { - url.searchParams.set('fields[avo_index_query]', selectedQuery) - url.searchParams.set('fields[avo_selected_all]', 'true') + const current = params[param] + url.searchParams.set(current.key, current.value) + + if (keys.selectedAllKey) { + url.searchParams.set(keys.selectedAllKey, current.selectedAll) } link.href = url.toString() } catch (error) { - console.error('Error updating link:', link, error) + console.error(`Error updating link (${param}):`, link, error) } }) } + setLinkParams(keys) { + return { + resourceIds: { + value: JSON.parse(this.element.dataset.selectedResources).join(','), + selectedAll: 'false', + key: keys.resourceIdsKey, + }, + selectedQuery: { + value: this.element.dataset.itemSelectAllSelectedAllQueryValue, + selectedAll: 'true', + key: keys.selectedQueryKey, + }, + } + } + resetUnselected() { this.selectedAllValue = false this.unselectedMessageTarget.classList.remove('hidden') diff --git a/config/routes.rb b/config/routes.rb index b8ca4bf9f2..8f929ea719 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,9 @@ get "resources", to: redirect(Avo.configuration.root_path) get "dashboards", to: redirect(Avo.configuration.root_path) + get "/bulk_update/edit", to: "bulk_update#edit", as: "edit_bulk_update" + post "/bulk_update/handle", to: "bulk_update#handle", as: "handle_bulk_update" + resources :media_library, only: [:index, :show, :update, :destroy], path: "media-library" get "attach-media", to: "media_library#attach" diff --git a/lib/avo/resources/base.rb b/lib/avo/resources/base.rb index c3de2b23f1..7eff8bfd9b 100644 --- a/lib/avo/resources/base.rb +++ b/lib/avo/resources/base.rb @@ -632,6 +632,7 @@ def entity_loader(entity) end def record_param + return nil if @record.nil? @record_param ||= @record.persisted? ? @record.to_param : nil end diff --git a/lib/generators/avo/templates/locales/avo.ar.yml b/lib/generators/avo/templates/locales/avo.ar.yml index 00c5b43005..717ea7f189 100644 --- a/lib/generators/avo/templates/locales/avo.ar.yml +++ b/lib/generators/avo/templates/locales/avo.ar.yml @@ -22,6 +22,11 @@ ar: attachment_class_detached: "%{attachment_class} تم فصل" attachment_destroyed: تم حذف المرفق attachment_failed: فشل في إرفاق %{attachment_class} + bulk_edit: تحرير جماعي + bulk_update: تحديث جماعي + bulk_update_failure: فشل في تحديث السجلات. + bulk_update_no_records: لا يمكن تنفيذ التحديث الجماعي بدون سجلات. + bulk_update_success: تم تنفيذ العملية الجماعية بنجاح. cancel: إلغاء choose_a_country: اختر دولة choose_an_option: اختر خيارًا diff --git a/lib/generators/avo/templates/locales/avo.de.yml b/lib/generators/avo/templates/locales/avo.de.yml index 35ea2ff2c3..0a30da9273 100644 --- a/lib/generators/avo/templates/locales/avo.de.yml +++ b/lib/generators/avo/templates/locales/avo.de.yml @@ -16,6 +16,11 @@ de: attachment_class_detached: "%{attachment_class} abgehängt." attachment_destroyed: Anhang gelöscht attachment_failed: "%{attachment_class} konnte nicht angehängt werden" + bulk_edit: Massenbearbeitung + bulk_update: Massenaktualisierung + bulk_update_failure: Aktualisierung der Datensätze fehlgeschlagen. + bulk_update_no_records: Bulk-Aktualisierung kann ohne Datensätze nicht durchgeführt werden. + bulk_update_success: Massenaktion erfolgreich ausgeführt. cancel: Abbrechen choose_a_country: Land auswählen choose_an_option: Option auswählen diff --git a/lib/generators/avo/templates/locales/avo.en.yml b/lib/generators/avo/templates/locales/avo.en.yml index b6fa650c94..eff925f26d 100644 --- a/lib/generators/avo/templates/locales/avo.en.yml +++ b/lib/generators/avo/templates/locales/avo.en.yml @@ -16,6 +16,11 @@ en: attachment_class_detached: "%{attachment_class} detached." attachment_destroyed: Attachment destroyed attachment_failed: Failed to attach %{attachment_class} + bulk_edit: Bulk edit + bulk_update: Bulk update + bulk_update_failure: Failed to update records. + bulk_update_no_records: Bulk update cannot be performed without records. + bulk_update_success: Bulk action run successfully. cancel: Cancel choose_a_country: Choose a country choose_an_option: Choose an option diff --git a/lib/generators/avo/templates/locales/avo.es.yml b/lib/generators/avo/templates/locales/avo.es.yml index 3b26bfa1ef..4d1abeebb2 100644 --- a/lib/generators/avo/templates/locales/avo.es.yml +++ b/lib/generators/avo/templates/locales/avo.es.yml @@ -18,6 +18,11 @@ es: attachment_class_detached: "%{attachment_class} adjuntado/a." attachment_destroyed: Adjunto eliminado attachment_failed: No se pudo adjuntar %{attachment_class} + bulk_edit: Edición masiva + bulk_update: Actualización masiva + bulk_update_failure: Error al actualizar los registros. + bulk_update_no_records: La actualización masiva no se puede realizar sin registros. + bulk_update_success: Acción masiva ejecutada con éxito. cancel: Cancelar choose_a_country: Elige un país choose_an_option: Elige una opción diff --git a/lib/generators/avo/templates/locales/avo.fr.yml b/lib/generators/avo/templates/locales/avo.fr.yml index 64cf8360c8..d03e05b121 100644 --- a/lib/generators/avo/templates/locales/avo.fr.yml +++ b/lib/generators/avo/templates/locales/avo.fr.yml @@ -18,6 +18,11 @@ fr: attachment_class_detached: "%{attachment_class} détaché." attachment_destroyed: Pièce jointe détruite attachment_failed: Échec de l'ajout de %{attachment_class} + bulk_edit: Modification en masse + bulk_update: Mise à jour en masse + bulk_update_failure: Échec de la mise à jour des enregistrements. + bulk_update_no_records: La mise à jour en masse ne peut pas être effectuée sans enregistrements. + bulk_update_success: Action en masse exécutée avec succès. cancel: Annuler choose_a_country: Sélectionnez un pays choose_an_option: Sélectionnez une option diff --git a/lib/generators/avo/templates/locales/avo.it.yml b/lib/generators/avo/templates/locales/avo.it.yml index 088ab48029..6473e21695 100644 --- a/lib/generators/avo/templates/locales/avo.it.yml +++ b/lib/generators/avo/templates/locales/avo.it.yml @@ -16,6 +16,11 @@ it: attachment_class_detached: "%{attachment_class} staccato." attachment_destroyed: Allegato distrutto attachment_failed: Impossibile allegare %{attachment_class} + bulk_edit: Modifica di massa + bulk_update: Aggiornamento di massa + bulk_update_failure: Aggiornamento dei record fallito. + bulk_update_no_records: L'aggiornamento di massa non può essere eseguito senza record. + bulk_update_success: Azione di massa eseguita con successo. cancel: Annulla choose_a_country: Scegli un paese choose_an_option: Scegli un'opzione diff --git a/lib/generators/avo/templates/locales/avo.ja.yml b/lib/generators/avo/templates/locales/avo.ja.yml index 8b6516a595..6fb27be7c7 100644 --- a/lib/generators/avo/templates/locales/avo.ja.yml +++ b/lib/generators/avo/templates/locales/avo.ja.yml @@ -18,6 +18,11 @@ ja: attachment_class_detached: "%{attachment_class}をデタッチしました。" attachment_destroyed: アタッチは削除されました attachment_failed: "%{attachment_class}の添付に失敗しました" + bulk_edit: 一括編集 + bulk_update: 一括更新 + bulk_update_failure: レコードの更新に失敗しました。 + bulk_update_no_records: レコードがない場合、バルク更新を実行できません。 + bulk_update_success: 一括処理が正常に実行されました。 cancel: キャンセル choose_a_country: 国を選択 choose_an_option: オプションを選択 diff --git a/lib/generators/avo/templates/locales/avo.nb.yml b/lib/generators/avo/templates/locales/avo.nb.yml index 3b5f59bb02..6a25a11d47 100644 --- a/lib/generators/avo/templates/locales/avo.nb.yml +++ b/lib/generators/avo/templates/locales/avo.nb.yml @@ -18,6 +18,11 @@ nb: attachment_class_detached: "%{attachment_class} fjernet." attachment_destroyed: Vedlett slettet attachment_failed: Kunne ikke legge ved %{attachment_class} + bulk_edit: Masse redigering + bulk_update: Masseoppdatering + bulk_update_failure: Feil ved oppdatering av poster. + bulk_update_no_records: Bulkoppdatering kan ikke utføres uten poster. + bulk_update_success: Massehandling utført med suksess. cancel: Avbryt choose_a_country: Velg et land choose_an_option: Velg et alternativ diff --git a/lib/generators/avo/templates/locales/avo.nl.yml b/lib/generators/avo/templates/locales/avo.nl.yml index dcaf35263c..23164a5644 100644 --- a/lib/generators/avo/templates/locales/avo.nl.yml +++ b/lib/generators/avo/templates/locales/avo.nl.yml @@ -16,6 +16,11 @@ nl: attachment_class_detached: "%{attachment_class} losgekoppeld." attachment_destroyed: Bijlage verwijderd attachment_failed: Kon %{attachment_class} niet bijvoegen + bulk_edit: Massabewerking + bulk_update: Massaupdate + bulk_update_failure: Fout bij het bijwerken van records. + bulk_update_no_records: Bulk update kan niet worden uitgevoerd zonder records. + bulk_update_success: Massactie succesvol uitgevoerd. cancel: Annuleren choose_a_country: Kies een land choose_an_option: Kies een optie diff --git a/lib/generators/avo/templates/locales/avo.nn.yml b/lib/generators/avo/templates/locales/avo.nn.yml index e5b9098427..a66492e1cc 100644 --- a/lib/generators/avo/templates/locales/avo.nn.yml +++ b/lib/generators/avo/templates/locales/avo.nn.yml @@ -18,6 +18,11 @@ nn: attachment_class_detached: "%{attachment_class} fjerna." attachment_destroyed: Vedlegg sletta attachment_failed: Klarte ikkje å legge ved %{attachment_class} + bulk_edit: Masseredigering + bulk_update: Masseoppdatering + bulk_update_failure: Feil ved oppdatering av poster. + bulk_update_no_records: Bulk update kan ikke utføres uten poster. + bulk_update_success: Massehandling utført med suksess. cancel: Avbryt choose_a_country: Vel eit land choose_an_option: Vel eit alternativ diff --git a/lib/generators/avo/templates/locales/avo.pl.yml b/lib/generators/avo/templates/locales/avo.pl.yml index 4f3a0189da..3dcf95b23c 100644 --- a/lib/generators/avo/templates/locales/avo.pl.yml +++ b/lib/generators/avo/templates/locales/avo.pl.yml @@ -16,6 +16,11 @@ pl: attachment_class_detached: "%{attachment_class} odłączony." attachment_destroyed: Załącznik usunięty attachment_failed: Nie udało się dołączyć %{attachment_class} + bulk_edit: Edycja zbiorcza + bulk_update: Aktualizacja zbiorcza + bulk_update_failure: Nie udało się zaktualizować rekordów. + bulk_update_no_records: Aktualizacja zbiorcza nie może zostać wykonana bez rekordów. + bulk_update_success: Akcja zbiorcza zakończona sukcesem. cancel: Anuluj choose_a_country: Wybierz kraj choose_an_option: Wybierz opcję diff --git a/lib/generators/avo/templates/locales/avo.pt-BR.yml b/lib/generators/avo/templates/locales/avo.pt-BR.yml index 82d220bb10..c44153cc87 100644 --- a/lib/generators/avo/templates/locales/avo.pt-BR.yml +++ b/lib/generators/avo/templates/locales/avo.pt-BR.yml @@ -18,6 +18,11 @@ pt-BR: attachment_class_detached: "%{attachment_class} separado." attachment_destroyed: Anexo destruído attachment_failed: Não foi possível anexar %{attachment_class} + bulk_edit: Edição em massa + bulk_update: Atualização em massa + bulk_update_failure: Falha ao atualizar os registros. + bulk_update_no_records: A atualização em massa não pode ser realizada sem registros. + bulk_update_success: Ação em massa executada com sucesso. cancel: Cancelar choose_a_country: Escolha um país choose_an_option: Escolha uma opção diff --git a/lib/generators/avo/templates/locales/avo.pt.yml b/lib/generators/avo/templates/locales/avo.pt.yml index bb163a138d..4660fce715 100644 --- a/lib/generators/avo/templates/locales/avo.pt.yml +++ b/lib/generators/avo/templates/locales/avo.pt.yml @@ -18,6 +18,11 @@ pt: attachment_class_detached: "%{attachment_class} separado." attachment_destroyed: Anexo destruído attachment_failed: Não foi possível anexar %{attachment_class} + bulk_edit: Edição em massa + bulk_update: Atualização em massa + bulk_update_failure: Falha ao atualizar os registros. + bulk_update_no_records: A atualização em massa não pode ser realizada sem registros. + bulk_update_success: Ação em massa executada com sucesso. cancel: Cancelar choose_a_country: Escolha um país choose_an_option: Escolha uma opção diff --git a/lib/generators/avo/templates/locales/avo.ro.yml b/lib/generators/avo/templates/locales/avo.ro.yml index 027d0fff1d..350af3a3ba 100644 --- a/lib/generators/avo/templates/locales/avo.ro.yml +++ b/lib/generators/avo/templates/locales/avo.ro.yml @@ -19,6 +19,11 @@ ro: attachment_class_detached: "%{attachment_class} separat." attachment_destroyed: Atașamentul a fost distrus attachment_failed: Nu s-a reușit atașarea %{attachment_class} + bulk_edit: Editare în masă + bulk_update: Actualizare în masă + bulk_update_failure: Actualizarea înregistrărilor a eșuat. + bulk_update_no_records: Actualizarea în masă nu poate fi efectuată fără înregistrări. + bulk_update_success: Acțiune în masă executată cu succes. cancel: Anulează choose_a_country: Alege o țară choose_an_option: Alege o opțiune diff --git a/lib/generators/avo/templates/locales/avo.ru.yml b/lib/generators/avo/templates/locales/avo.ru.yml index f939543240..e42c2961d5 100644 --- a/lib/generators/avo/templates/locales/avo.ru.yml +++ b/lib/generators/avo/templates/locales/avo.ru.yml @@ -16,6 +16,11 @@ ru: attachment_class_detached: "%{attachment_class} отсоединено." attachment_destroyed: Вложение удалено attachment_failed: Не удалось прикрепить %{attachment_class} + bulk_edit: Массовое редактирование + bulk_update: Массовое обновление + bulk_update_failure: Не удалось обновить записи. + bulk_update_no_records: Массовое обновление не может быть выполнено без записей. + bulk_update_success: Массовое действие выполнено успешно. cancel: Отмена choose_a_country: Выберите страну choose_an_option: Выберите опцию diff --git a/lib/generators/avo/templates/locales/avo.tr.yml b/lib/generators/avo/templates/locales/avo.tr.yml index edfcc498cb..7bf94d4738 100644 --- a/lib/generators/avo/templates/locales/avo.tr.yml +++ b/lib/generators/avo/templates/locales/avo.tr.yml @@ -18,6 +18,11 @@ tr: attachment_class_detached: "%{attachment_class} ilişkisi kesildi." attachment_destroyed: Ek silindi attachment_failed: "%{attachment_class} eklenemedi" + bulk_edit: Toplu düzenleme + bulk_update: Toplu güncelleme + bulk_update_failure: Kayıtlar güncellenemedi. + bulk_update_no_records: Kayıtlar olmadan toplu güncelleme yapılamaz. + bulk_update_success: Toplu işlem başarıyla tamamlandı. cancel: İptal et choose_a_country: Bir ülke seç choose_an_option: Bir seçenek seç diff --git a/lib/generators/avo/templates/locales/avo.uk.yml b/lib/generators/avo/templates/locales/avo.uk.yml index 4fdc4a0724..2817a72c3c 100644 --- a/lib/generators/avo/templates/locales/avo.uk.yml +++ b/lib/generators/avo/templates/locales/avo.uk.yml @@ -16,6 +16,11 @@ uk: attachment_class_detached: "%{attachment_class} відкріплено." attachment_destroyed: Вкладення знищено attachment_failed: Не вдалося прикріпити %{attachment_class} + bulk_edit: Масове редагування + bulk_update: Масове оновлення + bulk_update_failure: Не вдалося оновити записи. + bulk_update_no_records: Неможливо виконати масове оновлення без записів. + bulk_update_success: Масова дія успішно виконана. cancel: Скасувати choose_a_country: Виберіть країну choose_an_option: Виберіть опцію diff --git a/lib/generators/avo/templates/locales/avo.zh.yml b/lib/generators/avo/templates/locales/avo.zh.yml index 5910022bc0..9afc68df19 100644 --- a/lib/generators/avo/templates/locales/avo.zh.yml +++ b/lib/generators/avo/templates/locales/avo.zh.yml @@ -16,6 +16,11 @@ zh: attachment_class_detached: "%{attachment_class} 已分离。" attachment_destroyed: 附件已删除 attachment_failed: 无法附加 %{attachment_class} + bulk_edit: 批量编辑 + bulk_update: 批量更新 + bulk_update_failure: 更新记录失败。 + bulk_update_no_records: 无法在没有记录的情况下执行批量更新。 + bulk_update_success: 批量操作成功执行。 cancel: 取消 choose_a_country: 选择一个国家 choose_an_option: 选择一个选项 diff --git a/spec/system/avo/bulk_update_spec.rb b/spec/system/avo/bulk_update_spec.rb new file mode 100644 index 0000000000..d5eaee2400 --- /dev/null +++ b/spec/system/avo/bulk_update_spec.rb @@ -0,0 +1,84 @@ +require "rails_helper" + +RSpec.describe "Actions", type: :system do + let!(:user) { create :user } + let!(:project) { create :project } + let!(:second_project) { create :project } + + describe "check visibility" do + context "index" do + it "finds a button on index" do + visit "/admin/resources/projects" + expect(page).to have_link("Bulk update") + end + end + end + + describe "check redirect functionality" do + context "with selected records" do + it "redirects to edit form page" do + visit "/admin/resources/projects" + + check_and_select_projects + + find("a", text: "Bulk update").click + + expect(page).to have_current_path("/admin/bulk_update/edit", ignore_query: true) + expect(page).to have_button("Save") + end + end + + context "with no selected records" do + it "does not redirect to edit form page" do + visit "/admin/resources/projects" + find("a", text: "Bulk update").click + + expect(page).to have_current_path("/admin/resources/projects") + expect(page).to have_text "Bulk update cannot be performed without records." + end + end + end + + describe "check bulk update" do + before do + visit "/admin/resources/projects" + check_and_select_projects + find("a", text: "Bulk update").click + end + + context "with no changes in form" do + it "works correctly" do + find("button", text: "Save").click + + expect(page).to have_text "Bulk action run successfully." + end + end + + context "with valid params" do + it "works correctly" do + fill_in "project_name", with: "Test" + select "Virgin Islands, U.S.", from: "project_country" + find("button", text: "Save").click + + project.reload + second_project.reload + + expect(page).to have_current_path("/admin/resources/projects") + expect(page).to have_text "Bulk action run successfully." + expect(project.name).to eq "Test" + expect(second_project.country).to eq "VI" + end + end + end + + private + + def check_and_select_projects + checkboxes = all('input[type="checkbox"][name="Select item"]') + checkboxes[0].click + checkboxes[1].click + + expect(checkboxes[0].checked?).to be true + expect(checkboxes[1].checked?).to be true + end +end