diff --git a/app/components/avo/fields/select_field/edit_component.html.erb b/app/components/avo/fields/select_field/edit_component.html.erb index 2a845eeb5..6089578c0 100644 --- a/app/components/avo/fields/select_field/edit_component.html.erb +++ b/app/components/avo/fields/select_field/edit_component.html.erb @@ -1,6 +1,6 @@ <%= field_wrapper(**field_wrapper_args) do %> <%= @form.select @field.id, options, { - include_blank: @field.include_blank + include_blank: (params[:controller] == "avo/bulk_update") || @field.include_blank }, multiple: @field.multiple, aria: { diff --git a/app/components/avo/resource_component.rb b/app/components/avo/resource_component.rb index 46e71511d..55c26494f 100644 --- a/app/components/avo/resource_component.rb +++ b/app/components/avo/resource_component.rb @@ -146,6 +146,17 @@ def render_back_button(control) end end + def render_bulk_edit_button(control) + a_link bulk_edit_path, + style: :primary, + color: :primary, + icon: "avo/edit", + class: "hidden", + form_class: "flex flex-col sm:flex-row sm:inline-flex" do + control.label + end + end + def render_actions_list(actions_list) return unless can_see_the_actions_button? diff --git a/app/components/avo/views/resource_edit_component.rb b/app/components/avo/views/resource_edit_component.rb index 5ff3b6845..b5cbedf7c 100644 --- a/app/components/avo/views/resource_edit_component.rb +++ b/app/components/avo/views/resource_edit_component.rb @@ -8,6 +8,7 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent prop :actions, default: [].freeze prop :view, default: Avo::ViewInquirer.new(:edit).freeze prop :display_breadcrumbs, default: true, reader: :public + prop :query def after_initialize @display_breadcrumbs = @reflection.blank? && display_breadcrumbs @@ -72,7 +73,14 @@ def form_method end def form_url - if is_edit? + if params[:controller] == "avo/bulk_update" + helpers.handle_bulk_update_path( + resource_name: @resource.name, + fields: { + avo_resource_ids: params[:fields][:avo_resource_ids] + } + ) + elsif is_edit? helpers.resource_path( record: @resource.record, resource: @resource diff --git a/app/components/avo/views/resource_index_component.rb b/app/components/avo/views/resource_index_component.rb index e7a182d44..99224bc98 100644 --- a/app/components/avo/views/resource_index_component.rb +++ b/app/components/avo/views/resource_index_component.rb @@ -29,6 +29,20 @@ def title end 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_name: @resource.name, **args) + end + # The Create button is dependent on the new? policy method. # The create? should be called only when the user clicks the Save button so the developers gets access to the params from the form. def can_see_the_create_button? diff --git a/app/controllers/avo/base_controller.rb b/app/controllers/avo/base_controller.rb index 09076c46d..28bee8d3b 100644 --- a/app/controllers/avo/base_controller.rb +++ b/app/controllers/avo/base_controller.rb @@ -8,7 +8,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] @@ -406,8 +406,10 @@ 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 + 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 last_crumb_args = {} # If we're accessing this resource via another resource add the parent to the breadcrumbs. @@ -428,8 +430,16 @@ 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 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 000000000..f2dd158c7 --- /dev/null +++ b/app/controllers/avo/bulk_update_controller.rb @@ -0,0 +1,80 @@ +module Avo + class BulkUpdateController < ResourcesController + before_action :set_query, only: [:edit, :handle] + + def edit + @resource.hydrate(record: @resource.model_class.new(bulk_values)) + + set_component_for :bulk_edit, fallback_view: :edit + 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_to_apply = params[@resource_name.downcase.to_sym].compact_blank || {} + + @query.each do |record| + @resource.fill_record(record, params_to_apply) + 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 + + # This method returns a hash of the attributes of the model and their values + # If all the records have the same value for an attribute, the value is assigned to the attribute, otherwise nil is assigned + def bulk_values + @resource.model_class.attribute_names.map do |attribute_key| + values = @query.map { _1.public_send(attribute_key) }.uniq + value_to_assign = (values.size == 1) ? values.first : nil + + [attribute_key, value_to_assign] + end.to_h + end + + def set_query + resource_ids = params[:fields]&.dig(:avo_resource_ids)&.split(",") || [] + @query = decrypted_query || (resource_ids.any? ? @resource.find_record(resource_ids, params: params) : []) + 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 97e47b30f..42e5e1709 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 aac766d52..7b1d68fe8 100644 --- a/app/javascript/js/controllers/item_select_all_controller.js +++ b/app/javascript/js/controllers/item_select_all_controller.js @@ -19,6 +19,7 @@ export default class extends Controller { this.resourceName = this.element.dataset.resourceName this.selectedResourcesObserver = new AttributeObserver(this.element, 'data-selected-resources', this) this.selectedResourcesObserver.start() + this.updateBulkEditLinkVisibility() } elementAttributeValueChanged(element) { @@ -39,6 +40,7 @@ export default class extends Controller { // If some are selected, mark the checkbox as indeterminate. this.checkboxTarget.indeterminate = true } + this.updateBulkEditLinkVisibility() } disconnect() { @@ -71,6 +73,7 @@ export default class extends Controller { } this.updateLinks('resourceIds') + this.updateBulkEditLink('resourceIds') } selectAll(event) { @@ -88,25 +91,22 @@ export default class extends Controller { } updateLinks(param) { - const actionButtons = document.querySelectorAll(`a[data-actions-picker-target][data-resource-name="${this.resourceName}"]`) - actionButtons.forEach((link) => { - try { - const url = new URL(link.href) - - Array.from(url.searchParams.keys()) - .filter((key) => key.startsWith('fields[')) - .forEach((key) => url.searchParams.delete(key)) - - if (param === 'resourceIds') { - const resourceIds = JSON.parse(this.element.dataset.selectedResources).join(',') - url.searchParams.set('fields[avo_resource_ids]', resourceIds) - url.searchParams.set('fields[avo_selected_all]', 'false') - } else if (param === 'selectedQuery') { - const selectedQuery = this.element.dataset.itemSelectAllSelectedAllQueryValue - url.searchParams.set('fields[avo_index_query]', selectedQuery) - url.searchParams.set('fields[avo_selected_all]', 'true') - } + this.updateActionLinks(param, '[data-target="actions-list"] > a') + } + + updateBulkEditLink(param) { + this.updateActionLinks(param, 'a[href*="/admin/bulk_update/edit"]') + this.updateBulkEditLinkVisibility() + } + updateActionLinks(param, selector) { + const selectedResourcesArray = JSON.parse(this.element.dataset.selectedResources) + const selectedResources = selectedResourcesArray.join(',') + const selectedQuery = this.element.dataset.itemSelectAllSelectedAllQueryValue + + document.querySelectorAll(selector).forEach((link) => { + try { + const url = this.buildUpdatedUrl(link, param, selectedResources, selectedQuery) link.href = url.toString() } catch (error) { console.error('Error updating link:', link, error) @@ -114,6 +114,44 @@ export default class extends Controller { }) } + buildUpdatedUrl(link, param, selectedResources, selectedQuery) { + const url = new URL(link.href) + + // Remove old field parameters + Array.from(url.searchParams.keys()) + .filter((key) => key.startsWith('fields[')) + .forEach((key) => url.searchParams.delete(key)) + + const isBulkUpdate = url.pathname.includes('/admin/bulk_update/edit') + const resourceIdsKey = 'fields[avo_resource_ids]' + const selectedQueryKey = isBulkUpdate ? 'fields[avo_selected_query]' : 'fields[avo_index_query]' + const selectedAllKey = 'fields[avo_selected_all]' + + if (param === 'resourceIds') { + url.searchParams.set(resourceIdsKey, selectedResources) + url.searchParams.set(selectedAllKey, 'false') + } else if (param === 'selectedQuery') { + url.searchParams.set(selectedQueryKey, selectedQuery) + url.searchParams.set(selectedAllKey, 'true') + } + + return url + } + + updateBulkEditLinkVisibility() { + const bulkUpdateLink = document.querySelector('a[href*="/admin/bulk_update/edit"]') + if (!bulkUpdateLink) return + + const selectedResourcesArray = JSON.parse(this.element.dataset.selectedResources) + const resourceCount = selectedResourcesArray.length + + if (resourceCount >= 2) { + bulkUpdateLink.classList.remove('hidden') + } else { + bulkUpdateLink.classList.add('hidden') + } + } + resetUnselected() { this.selectedAllValue = false this.unselectedMessageTarget.classList.remove('hidden') diff --git a/config/routes.rb b/config/routes.rb index cc16a22d9..f5e2f9cde 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,9 @@ get "resources", to: redirect(Avo.configuration.root_path), as: :avo_resources_redirect get "dashboards", to: redirect(Avo.configuration.root_path), as: :avo_dashboards_redirect + get "/bulk_update/edit", to: "bulk_update#edit", as: "edit_bulk_update" + put "/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/concerns/has_controls.rb b/lib/avo/concerns/has_controls.rb index 50176234f..0a51634e7 100644 --- a/lib/avo/concerns/has_controls.rb +++ b/lib/avo/concerns/has_controls.rb @@ -21,7 +21,7 @@ def render_edit_controls end def render_index_controls(item:) - [BackButton.new, AttachButton.new(item: item), ActionsList.new(as_index_control: true), CreateButton.new(item: item)] + [BackButton.new, AttachButton.new(item: item), BulkEditButton.new, ActionsList.new(as_index_control: true), CreateButton.new(item: item)] end def render_row_controls(item:) diff --git a/lib/avo/resources/controls/bulk_edit_button.rb b/lib/avo/resources/controls/bulk_edit_button.rb new file mode 100644 index 000000000..05dfadf4d --- /dev/null +++ b/lib/avo/resources/controls/bulk_edit_button.rb @@ -0,0 +1,16 @@ +module Avo + module Resources + module Controls + class BulkEditButton < BaseControl + def initialize(**args) + super(**args) + + @label = args[:label] || I18n.t("avo.bulk_edit").capitalize + @icon = args[:icon] || "avo/edit" + @style = args[:style] || :primary + @color = args[:color] || :primary + end + end + end + end +end diff --git a/lib/generators/avo/templates/locales/avo.ar.yml b/lib/generators/avo/templates/locales/avo.ar.yml index 46915812f..179b2701d 100644 --- a/lib/generators/avo/templates/locales/avo.ar.yml +++ b/lib/generators/avo/templates/locales/avo.ar.yml @@ -22,6 +22,9 @@ ar: attachment_class_detached: "%{attachment_class} تم فصل" attachment_destroyed: تم حذف المرفق attachment_failed: فشل في إرفاق %{attachment_class} + bulk_edit: تحرير جماعي + bulk_update_failure: فشل في تحديث السجلات. + 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 9af7557f7..7a8c52bd7 100644 --- a/lib/generators/avo/templates/locales/avo.de.yml +++ b/lib/generators/avo/templates/locales/avo.de.yml @@ -16,6 +16,9 @@ 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_failure: Aktualisierung der Datensätze fehlgeschlagen. + 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 a86659e09..e6a5836c4 100644 --- a/lib/generators/avo/templates/locales/avo.en.yml +++ b/lib/generators/avo/templates/locales/avo.en.yml @@ -16,6 +16,9 @@ en: attachment_class_detached: "%{attachment_class} detached." attachment_destroyed: Attachment destroyed attachment_failed: Failed to attach %{attachment_class} + bulk_edit: Bulk edit + bulk_update_failure: Failed to update 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 000cd09f3..f0192bb48 100644 --- a/lib/generators/avo/templates/locales/avo.es.yml +++ b/lib/generators/avo/templates/locales/avo.es.yml @@ -18,6 +18,9 @@ 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_failure: Error al actualizar los 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 4e8ed58f9..dc2c0d6c2 100644 --- a/lib/generators/avo/templates/locales/avo.fr.yml +++ b/lib/generators/avo/templates/locales/avo.fr.yml @@ -18,6 +18,9 @@ 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_failure: Échec de la mise à jour des 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 b9c89a6dc..3d615e112 100644 --- a/lib/generators/avo/templates/locales/avo.it.yml +++ b/lib/generators/avo/templates/locales/avo.it.yml @@ -16,6 +16,9 @@ it: attachment_class_detached: "%{attachment_class} staccato." attachment_destroyed: Allegato distrutto attachment_failed: Impossibile allegare %{attachment_class} + bulk_edit: Modifica di massa + bulk_update_failure: Aggiornamento dei record fallito. + 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 e9332831e..e6108b70d 100644 --- a/lib/generators/avo/templates/locales/avo.ja.yml +++ b/lib/generators/avo/templates/locales/avo.ja.yml @@ -18,6 +18,9 @@ ja: attachment_class_detached: "%{attachment_class}をデタッチしました。" attachment_destroyed: アタッチは削除されました attachment_failed: "%{attachment_class}の添付に失敗しました" + bulk_edit: 一括編集 + bulk_update_failure: レコードの更新に失敗しました。 + 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 67b0db417..afd7a7c41 100644 --- a/lib/generators/avo/templates/locales/avo.nb.yml +++ b/lib/generators/avo/templates/locales/avo.nb.yml @@ -18,6 +18,9 @@ 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_failure: Feil ved oppdatering av 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 624fec303..65c32e26d 100644 --- a/lib/generators/avo/templates/locales/avo.nl.yml +++ b/lib/generators/avo/templates/locales/avo.nl.yml @@ -16,6 +16,9 @@ nl: attachment_class_detached: "%{attachment_class} losgekoppeld." attachment_destroyed: Bijlage verwijderd attachment_failed: Kon %{attachment_class} niet bijvoegen + bulk_edit: Massabewerking + bulk_update_failure: Fout bij het bijwerken van 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 d00ffa19e..e36656e0b 100644 --- a/lib/generators/avo/templates/locales/avo.nn.yml +++ b/lib/generators/avo/templates/locales/avo.nn.yml @@ -18,6 +18,9 @@ nn: attachment_class_detached: "%{attachment_class} fjerna." attachment_destroyed: Vedlegg sletta attachment_failed: Klarte ikkje å legge ved %{attachment_class} + bulk_edit: Masseredigering + bulk_update_failure: Feil ved oppdatering av 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 d640a042d..c82cebc10 100644 --- a/lib/generators/avo/templates/locales/avo.pl.yml +++ b/lib/generators/avo/templates/locales/avo.pl.yml @@ -16,6 +16,9 @@ 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_failure: Nie udało się zaktualizować 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 f7879050e..5f8ecfd52 100644 --- a/lib/generators/avo/templates/locales/avo.pt-BR.yml +++ b/lib/generators/avo/templates/locales/avo.pt-BR.yml @@ -18,6 +18,9 @@ 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_failure: Falha ao atualizar os 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 5d4b2c06b..1fe413ca9 100644 --- a/lib/generators/avo/templates/locales/avo.pt.yml +++ b/lib/generators/avo/templates/locales/avo.pt.yml @@ -18,6 +18,9 @@ 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_failure: Falha ao atualizar os 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 c2a53432d..28ee7f632 100644 --- a/lib/generators/avo/templates/locales/avo.ro.yml +++ b/lib/generators/avo/templates/locales/avo.ro.yml @@ -19,6 +19,9 @@ 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_failure: Actualizarea înregistrărilor a eșuat. + 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 cc5d6936b..16197b2f9 100644 --- a/lib/generators/avo/templates/locales/avo.ru.yml +++ b/lib/generators/avo/templates/locales/avo.ru.yml @@ -16,6 +16,9 @@ ru: attachment_class_detached: "%{attachment_class} отсоединено." attachment_destroyed: Вложение удалено attachment_failed: Не удалось прикрепить %{attachment_class} + bulk_edit: Массовое редактирование + bulk_update_failure: Не удалось обновить записи. + 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 f21a899f3..561fd21c7 100644 --- a/lib/generators/avo/templates/locales/avo.tr.yml +++ b/lib/generators/avo/templates/locales/avo.tr.yml @@ -18,6 +18,9 @@ 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_failure: Kayıtlar güncellenemedi. + 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 f43463995..d52ce289c 100644 --- a/lib/generators/avo/templates/locales/avo.uk.yml +++ b/lib/generators/avo/templates/locales/avo.uk.yml @@ -16,6 +16,9 @@ uk: attachment_class_detached: "%{attachment_class} відкріплено." attachment_destroyed: Вкладення знищено attachment_failed: Не вдалося прикріпити %{attachment_class} + bulk_edit: Масове редагування + bulk_update_failure: Не вдалося оновити записи. + bulk_update_success: Масова дія успішно виконана. cancel: Скасувати choose_a_country: Виберіть країну choose_an_option: Виберіть опцію diff --git a/lib/generators/avo/templates/locales/avo.zh-TW.yml b/lib/generators/avo/templates/locales/avo.zh-TW.yml index 3def31981..d5346f91b 100644 --- a/lib/generators/avo/templates/locales/avo.zh-TW.yml +++ b/lib/generators/avo/templates/locales/avo.zh-TW.yml @@ -16,6 +16,9 @@ zh-TW: attachment_class_detached: "%{attachment_class} 已分離。" attachment_destroyed: 附件已刪除 attachment_failed: 無法附加 %{attachment_class} + bulk_edit: 批量編輯 + bulk_update_failure: 記錄更新失敗。 + 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 85fcb7074..c8a37aa96 100644 --- a/lib/generators/avo/templates/locales/avo.zh.yml +++ b/lib/generators/avo/templates/locales/avo.zh.yml @@ -16,6 +16,9 @@ zh: attachment_class_detached: "%{attachment_class} 已分离。" attachment_destroyed: 附件已删除 attachment_failed: 无法附加 %{attachment_class} + bulk_edit: 批量编辑 + bulk_update_failure: 更新记录失败。 + bulk_update_success: 批量操作成功执行。 cancel: 取消 choose_a_country: 选择一个国家 choose_an_option: 选择一个选项 diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 2040317c8..acd4c9fbc 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -174,7 +174,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "price_cents", default: 0, null: false - t.string "price_currency", default: "USD", null: false + t.string "price_currency", default: "'USD'::character varying", null: false t.string "sizes", default: [], array: true end diff --git a/spec/system/avo/bulk_update_spec.rb b/spec/system/avo/bulk_update_spec.rb new file mode 100644 index 000000000..24f4b72e1 --- /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 + before do + visit "/admin/resources/projects" + end + it "does not finds a button on index" do + expect(page).not_to have_link("Bulk edit") + end + it "button appears after check projects" do + check_and_select_projects + expect(page).to have_link("Bulk edit") + 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 edit").click + + expect(page).to have_current_path("/admin/bulk_update/edit", ignore_query: true) + expect(page).to have_button("Save") + end + end + end + + describe "check bulk update" do + before do + visit "/admin/resources/projects" + check_and_select_projects + find("a", text: "Bulk edit").click + end + + context "with no changes in form" do + it "works correctly" do + visit "/admin/resources/projects" + check_and_select_projects + find("a", text: "Bulk edit").click + + 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 "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