From 4ddf534a0c8f163cd8b1e52f027fdcba6e0f6c8c Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Mon, 17 Aug 2026 23:24:39 +0200 Subject: [PATCH] Carica oggetto e corpo dell'email appena si sceglie un template A/B. Co-authored-by: Cursor --- app/controllers/mailings_controller.rb | 11 ++- app/helpers/application_helper.rb | 4 + .../controllers/template_picker_controller.js | 74 +++++++++++++++++++ app/views/mailings/_form.html.erb | 41 +++++----- test/controllers/mailings_controller_test.rb | 49 ++++++++++++ 5 files changed, 150 insertions(+), 29 deletions(-) create mode 100644 app/javascript/controllers/template_picker_controller.js diff --git a/app/controllers/mailings_controller.rb b/app/controllers/mailings_controller.rb index 518d57a..5ee2b45 100644 --- a/app/controllers/mailings_controller.rb +++ b/app/controllers/mailings_controller.rb @@ -166,19 +166,18 @@ class MailingsController < ApplicationController end def apply_template_if_needed - copy_template(params.dig(:mailing, :mail_template_id), :subject, :body_html, params[:use_template_content]) - copy_template(params.dig(:mailing, :mail_template_b_id), :subject_b, :body_html_b, params[:use_template_b_content]) + copy_template_if_blank(params.dig(:mailing, :mail_template_id), :subject, :body_html) + copy_template_if_blank(params.dig(:mailing, :mail_template_b_id), :subject_b, :body_html_b) end - def copy_template(template_id, subject_attr, body_attr, flag) + def copy_template_if_blank(template_id, subject_attr, body_attr) return if template_id.blank? - return unless ActiveModel::Type::Boolean.new.cast(flag) template = MailTemplate.for_project(current_project).find_by(id: template_id) return unless template - @mailing.public_send("#{subject_attr}=", template.subject) - @mailing.public_send("#{body_attr}=", template.body_html) + @mailing.public_send("#{subject_attr}=", template.subject) if @mailing.public_send(subject_attr).blank? + @mailing.public_send("#{body_attr}=", template.body_html) if @mailing.public_send(body_attr).blank? end def rebuild_recipients_if_needed diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 376a906..f10c715 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -21,6 +21,10 @@ module ApplicationHelper "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none focus:ring-1 focus:ring-zinc-400 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500 dark:focus:ring-zinc-500" end + def mail_templates_picker_payload(templates) + Array(templates).map { |template| { id: template.id, subject: template.subject, body_html: template.body_html } } + end + def card_class "rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100" end diff --git a/app/javascript/controllers/template_picker_controller.js b/app/javascript/controllers/template_picker_controller.js new file mode 100644 index 0000000..c3e1c40 --- /dev/null +++ b/app/javascript/controllers/template_picker_controller.js @@ -0,0 +1,74 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["select", "subject"] + static values = { templates: Array } + + connect() { + this.pendingHtml = null + this.onEditorReady = this.onEditorReady.bind(this) + this.editorElement?.addEventListener("trix-initialize", this.onEditorReady) + if (this.shouldPrefill()) this.apply() + } + + disconnect() { + this.editorElement?.removeEventListener("trix-initialize", this.onEditorReady) + } + + apply() { + const template = this.currentTemplate() + if (!template) return + + if (this.hasSubjectTarget) { + this.subjectTarget.value = template.subject || "" + this.subjectTarget.dispatchEvent(new Event("input", { bubbles: true })) + } + + this.setBody(template.body_html || "") + } + + shouldPrefill() { + if (!this.selectTarget.value) return false + const subjectBlank = !this.hasSubjectTarget || this.subjectTarget.value.trim() === "" + return subjectBlank && this.bodyIsBlank() + } + + currentTemplate() { + const id = Number(this.selectTarget.value) + if (!id) return null + return (this.templatesValue || []).find((template) => Number(template.id) === id) || null + } + + setBody(html) { + this.pendingHtml = html + const editor = this.editorElement?.editor + if (editor) { + editor.loadHTML(html) + this.pendingHtml = null + return + } + if (this.hiddenInput) this.hiddenInput.value = html + } + + onEditorReady() { + if (this.pendingHtml != null) { + this.editorElement.editor?.loadHTML(this.pendingHtml) + this.pendingHtml = null + return + } + if (this.shouldPrefill()) this.apply() + } + + bodyIsBlank() { + const value = this.hiddenInput?.value || "" + return value.replace(/<[^>]*>/g, "").replace(/ /g, " ").trim() === "" + } + + get editorElement() { + return this.element.querySelector("trix-editor") + } + + get hiddenInput() { + return this.element.querySelector("[data-wysiwyg-target='input']") + } +} diff --git a/app/views/mailings/_form.html.erb b/app/views/mailings/_form.html.erb index 73d8ec6..c5e1490 100644 --- a/app/views/mailings/_form.html.erb +++ b/app/views/mailings/_form.html.erb @@ -37,7 +37,7 @@ <%= f.check_box :ab_test, { data: { ab_test_target: "checkbox", action: "ab-test#toggle" } } %> Test A/B su questa comunicazione - Due versioni di oggetto e HTML. Ogni destinatario riceve A o B. + Due versioni della stessa email. Carica un template A e un template B nelle colonne: ogni destinatario ne riceve una. @@ -48,48 +48,43 @@ + <% templates_payload = mail_templates_picker_payload(@mail_templates) %>
" data-ab-test-target="grid"> -
+ <%= tag.div class: "space-y-4", data: { controller: "template-picker", template_picker_templates_value: templates_payload } do %>

Variante A

- - <%= f.collection_select :mail_template_id, @mail_templates, :id, :name, { include_blank: "Nessuno" }, { class: input_class } %> + + <%= f.collection_select :mail_template_id, @mail_templates, :id, :name, + { include_blank: "Nessuno — scrivi a mano" }, + { class: input_class, data: { template_picker_target: "select", action: "change->template-picker#apply" } } %> +

Scegliendo un template, oggetto e testo si copiano qui sotto. Poi puoi modificarli solo per questo invio.

- <% unless mailing.new_record? %> - - <% end %>
- <%= f.text_field :subject, required: true, class: input_class %> + <%= f.text_field :subject, required: true, class: input_class, data: { template_picker_target: "subject" } %>
<%= render "shared/wysiwyg_field", form: f, method: :body_html, label: "Corpo HTML A" %>
-
+ <% end %> -
" data-ab-test-target="panel"> + <%= tag.div class: "space-y-4 #{'hidden' unless mailing.ab_test?}", data: { controller: "template-picker", template_picker_templates_value: templates_payload, ab_test_target: "panel" } do %>

Variante B

- - <%= f.collection_select :mail_template_b_id, @mail_templates, :id, :name, { include_blank: "Nessuno" }, { class: input_class } %> + + <%= f.collection_select :mail_template_b_id, @mail_templates, :id, :name, + { include_blank: "Nessuno — scrivi a mano" }, + { class: input_class, data: { template_picker_target: "select", action: "change->template-picker#apply" } } %> +

Stesso meccanismo della variante A: il template riempie oggetto e corpo B.

- <% unless mailing.new_record? %> - - <% end %>
- <%= f.text_field :subject_b, class: input_class %> + <%= f.text_field :subject_b, class: input_class, data: { template_picker_target: "subject" } %>
<%= render "shared/wysiwyg_field", form: f, method: :body_html_b, label: "Corpo HTML B" %>
-
+ <% end %>
diff --git a/test/controllers/mailings_controller_test.rb b/test/controllers/mailings_controller_test.rb index b874db2..62e536d 100644 --- a/test/controllers/mailings_controller_test.rb +++ b/test/controllers/mailings_controller_test.rb @@ -104,4 +104,53 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest body = (mail.html_part || mail).body.to_s assert_match(/Versione B/, body) end + + test "new mailing form embeds template content for the picker" do + login_as users(:admin) + follow_redirect! if response.redirect? + template = MailTemplate.create!( + project: @project, + name: "Variante B picker", + subject: "Oggetto dal template B", + body_html: "

Corpo dal template B

" + ) + + get new_mailing_path(project_code: @project.code) + assert_response :success + assert_match(/template-picker/, response.body) + assert_match(/Oggetto dal template B/, response.body) + assert_match(/Corpo dal template B/, response.body) + assert_includes response.body, template.id.to_s + end + + test "create copies blank variant B fields from selected template" do + login_as users(:admin) + follow_redirect! if response.redirect? + template_b = MailTemplate.create!( + project: @project, + name: "Template B server", + subject: "B {{societa}}", + body_html: "

Versione B dal template

" + ) + + post mailings_path(project_code: @project.code), params: { + mailing: { + name: "AB da template", + mail_identity_id: @identity.id, + mail_template_b_id: template_b.id, + audience: "to_send", + ab_test: "1", + ab_assignment: "from_record", + subject: "Oggetto A", + body_html: "

Versione A

", + subject_b: "", + body_html_b: "", + interval_seconds: 0 + } + } + mailing = Mailing.last + assert_redirected_to mailing_path(mailing, project_code: @project.code) + assert_equal "B {{societa}}", mailing.subject_b + assert_includes mailing.body_html_b, "Versione B dal template" + end end