diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 55dae8b..3debc85 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -89,6 +89,24 @@ html.dark .kanban-board::-webkit-scrollbar-thumb:hover { html { color-scheme: light; + -webkit-text-size-adjust: 100%; +} + +body { + overflow-x: hidden; +} + +.break-anywhere { + overflow-wrap: anywhere; + word-break: break-word; +} + +@media (max-width: 767px) { + .btn-primary, + .btn-secondary, + .btn-danger { + min-height: 2.75rem; + } } html.dark { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f10c715..50725bf 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -6,19 +6,23 @@ module ApplicationHelper end def btn_primary - "btn-primary inline-flex items-center justify-center rounded-lg px-3.5 py-2 text-sm font-medium" + "btn-primary inline-flex min-h-11 items-center justify-center rounded-lg px-3.5 py-2.5 text-sm font-medium" end def btn_secondary - "btn-secondary inline-flex items-center justify-center rounded-lg px-3.5 py-2 text-sm font-medium" + "btn-secondary inline-flex min-h-11 items-center justify-center rounded-lg px-3.5 py-2.5 text-sm font-medium" end def btn_danger - "btn-danger inline-flex items-center justify-center rounded-lg px-3.5 py-2 text-sm font-medium" + "btn-danger inline-flex min-h-11 items-center justify-center rounded-lg px-3.5 py-2.5 text-sm font-medium" + end + + def chip_action_class + "inline-flex min-h-11 items-center justify-center rounded-lg border border-zinc-300 bg-zinc-50 px-3 py-2 text-sm font-medium text-zinc-800 hover:bg-zinc-100 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700" end def input_class - "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" + "w-full min-h-11 rounded-lg border border-zinc-200 bg-white px-3 py-2.5 text-base text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none focus:ring-1 focus:ring-zinc-400 md:text-sm 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) @@ -33,6 +37,46 @@ module ApplicationHelper "min-w-full text-sm text-zinc-900 dark:text-zinc-100" end + def table_wrap_class + "overflow-x-auto overscroll-x-contain" + end + + def user_initials(user) + "#{user.first_name.to_s[0]}#{user.last_name.to_s[0]}".upcase + end + + def tel_link(number, **options) + return "—" if number.blank? + + link_to number, "tel:#{number.to_s.gsub(/\s+/, '')}", **options + end + + def mailto_link(email, **options) + return "—" if email.blank? + + link_to email, "mailto:#{email}", **options + end + + def nav_item_active?(path, controllers: nil) + return true if controllers&.include?(controller_name) + + home = begin + project_root_path + rescue StandardError + root_path + end + current_page?(path) || (path != home && request.fullpath.start_with?(path.split("?").first)) + end + + def mobile_tab_class(path, controllers: nil) + base = "flex min-h-12 min-w-0 flex-1 flex-col items-center justify-center gap-0.5 px-1 py-1 text-[11px] font-medium" + if nav_item_active?(path, controllers: controllers) + "#{base} text-zinc-900 dark:text-zinc-50" + else + "#{base} text-zinc-500 dark:text-zinc-400" + end + end + def status_badge(status) colors = { "prospect" => "bg-sky-100 text-sky-800", @@ -95,13 +139,7 @@ module ApplicationHelper end def nav_link(label, path, icon: nil) - home = begin - project_root_path - rescue StandardError - root_path - end - active = current_page?(path) || (path != home && request.fullpath.start_with?(path.split("?").first)) - classes = if active + classes = if nav_item_active?(path) "bg-zinc-900 text-white dark:bg-zinc-100 dark:text-zinc-900" else "text-zinc-600 hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100" diff --git a/app/javascript/controllers/sheet_controller.js b/app/javascript/controllers/sheet_controller.js new file mode 100644 index 0000000..ee21e80 --- /dev/null +++ b/app/javascript/controllers/sheet_controller.js @@ -0,0 +1,35 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["panel"] + + connect() { + this.boundKey = this.onKey.bind(this) + document.addEventListener("keydown", this.boundKey) + } + + disconnect() { + document.removeEventListener("keydown", this.boundKey) + document.body.classList.remove("overflow-hidden") + } + + toggle(event) { + event?.preventDefault() + event?.stopPropagation() + this.panelTarget.classList.contains("hidden") ? this.show() : this.hide() + } + + show() { + this.panelTarget.classList.remove("hidden") + document.body.classList.add("overflow-hidden") + } + + hide() { + this.panelTarget.classList.add("hidden") + document.body.classList.remove("overflow-hidden") + } + + onKey(event) { + if (event.key === "Escape") this.hide() + } +} diff --git a/app/views/contacts/index.html.erb b/app/views/contacts/index.html.erb index 17dbdf5..b8c6f7b 100644 --- a/app/views/contacts/index.html.erb +++ b/app/views/contacts/index.html.erb @@ -1,31 +1,50 @@
| Nome | Organizzazione | Telefono | |
|---|---|---|---|
| <%= link_to c.full_name, c.organization, class: "hover:underline" %><% if c.primary_contact? %> ★<% end %> | -<%= c.organization.name %> | -<%= c.email %> | -<%= c.phone.presence || c.mobile %> | -
Nessun contatto.
+ <% end %> +| Nome | Organizzazione | Telefono | |
|---|---|---|---|
| <%= link_to c.full_name, c.organization, class: "hover:underline" %><% if c.primary_contact? %> ★<% end %> | +<%= c.organization.name %> | +<%= c.email %> | +<%= c.phone.presence || c.mobile %> | +
Organizzazione: <%= link_to @contact.organization.name, @contact.organization %>
-Ruolo: <%= @contact.role %>
-Email: <%= @contact.email %>
-Telefono: <%= @contact.phone %>
-Cellulare: <%= @contact.mobile %>
- <%= link_to "Modifica", edit_contact_path(@contact), class: "mt-4 inline-block text-emerald-700 dark:text-emerald-400" %> +<%= link_to @contact.organization.name, @contact.organization, class: "hover:underline" %>
+Cosa devo fare oggi per trasformare i prospect in clienti?
<%= link_to "Impostazioni", admin_path, class: "hover:underline" %> · da questi indirizzi partono le campagne. Le password SMTP sono cifrate.
Nessun account. Crea il mittente con host, porta e credenziali SMTP.
<% else %> -| Nome | @@ -44,6 +62,8 @@ <% end %>
|---|
<%= link_to "Invii", dashboard_mailings_path, class: "hover:underline" %> · HTML riutilizzabile con variabili della scheda cliente.
Nessun template. Creane uno e usalo come base per gli invii.
<% else %> -| Nome | @@ -36,6 +52,8 @@ <% end %>
|---|
Stato delle campagne, prove e invii di massa scaglionati.
Nessun invio. Crea una bozza, fai una prova con dati manuali e poi autorizza l’invio di massa.
<% else %> -| Invio | @@ -107,7 +129,9 @@
|---|
<%= mailing_status_badge(@mailing.status) %> · <%= @mailing.audience_label %> @@ -25,8 +25,8 @@
<% end %>| - - | +Organizzazione | -Test | - <% end %> -Stato | -- | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| - <% if recipient.email_ok? %> - <%= check_box_tag "pending_ids[]", recipient.id, recipient.pending?, - id: "recipient_#{recipient.id}", - data: { check_all_target: "checkbox" } %> - <% else %> - — - <% end %> - | - <% end %> -
+
+
<%= link_to recipient.organization.name, recipient.organization, class: "font-medium hover:underline" %>
- <%= recipient.contact&.full_name.presence || "Nessun contatto" %>
- |
-
+ <%= mailing_recipient_status_badge(recipient.status) %>
+ <% if @mailing.ab_test? %><%= ab_variant_badge(recipient.ab_variant) %><% end %>
+
+
+ <%= recipient.contact&.full_name.presence || "Nessun contatto" %>
+ ·
<% if recipient.email_ok? %>
<%= recipient.email %>
<% else %>
<%= recipient.email.presence || "manca email" %>
<% end %>
- <% if recipient.skip_reason.present? && !recipient.pending? %>
- <%= recipient.skip_reason %>
- <% end %>
- <% if recipient.error_message.present? %>
- <%= recipient.error_message %>
- <% end %>
- |
- <% if @mailing.ab_test? %>
- <%= ab_variant_badge(recipient.ab_variant) || "—" %> | + + <% if recipient.skip_reason.present? && !recipient.pending? %> +<%= mailing_recipient_status_badge(recipient.status) %> | -
- <% if recipient.email_ok? %>
- <%= link_to "Anteprima", mailing_path(@mailing, recipient_id: recipient.id, variant: recipient.ab_variant), class: "text-zinc-700 hover:underline dark:text-zinc-300" %>
+ <% if recipient.error_message.present? %>
+ <%= recipient.error_message %>
+ <% end %>
+ <% if recipient.email_ok? %>
+ <%= link_to "Anteprima", mailing_path(@mailing, recipient_id: recipient.id, variant: recipient.ab_variant), class: "mt-2 inline-block text-sm text-zinc-700 hover:underline dark:text-zinc-300" %>
+ <% end %>
+
+
+ <% end %>
+
+
+
+
+
Stato |
+
+ | | |||
|
+ <% if @mailing.editable? && recipient.email_ok? %>
+ <%= check_box_tag "pending_ids[]", recipient.id, recipient.pending?,
+ id: "recipient_desktop_#{recipient.id}",
+ class: "mr-2",
+ data: { check_all_target: "checkbox" } %>
+ <% end %>
+ <%= link_to recipient.organization.name, recipient.organization, class: "font-medium hover:underline" %>
+ <%= recipient.contact&.full_name.presence || "Nessun contatto" %>
+ |
+
+ <% if recipient.email_ok? %>
+ <%= recipient.email %>
+ <% else %>
+ <%= recipient.email.presence || "manca email" %>
+ <% end %>
+ <% if recipient.skip_reason.present? && !recipient.pending? %>
+ <%= recipient.skip_reason %>
+ <% end %>
+ <% if recipient.error_message.present? %>
+ <%= recipient.error_message %>
+ <% end %>
+ |
+ <% if @mailing.ab_test? %>
+ <%= ab_variant_badge(recipient.ab_variant) || "—" %> | + <% end %> +<%= mailing_recipient_status_badge(recipient.status) %> | ++ <% if recipient.email_ok? %> + <%= link_to "Anteprima", mailing_path(@mailing, recipient_id: recipient.id, variant: recipient.ab_variant), class: "text-zinc-700 hover:underline dark:text-zinc-300" %> + <% end %> + | +
| Organizzazione | Nome | Stage | Valore | Owner |
|---|
| N. | -Organizzazione | -Regione | -M/F | -Streaming | -Test | -Stage | -Invio | -||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <%= org.list_position || "—" %> | -
- <%= link_to org.name, org, class: "font-medium text-zinc-900 hover:underline dark:text-zinc-100" %>
- <%= org.email.presence || org.organization_type_label %>
- |
- <%= [org.region, org.province].compact_blank.join(" · ").presence || "—" %> | -<%= org.team_gender_label.presence || "—" %> | -<%= streaming_badge(org.streaming_status) || "—" %> | - <% opp = org.campaign_opportunity(current_project) %> -<%= opp&.ab_variant.presence || "—" %> | -<%= opp ? stage_badge(opp.pipeline_stage) : "—" %> | -<%= opp&.send_status_label.presence || "—" %> | +
| N. | +Organizzazione | +Regione | +M/F | +Streaming | +Test | +Stage | +Invio |
|---|
| Fonte | -Lead | -Opp. | -Clienti | -Conv. | -
|---|---|---|---|---|
| <%= row[:label] %> | -<%= row[:leads] %> | -<%= row[:opportunities] %> | -<%= row[:customers] %> | -<%= row[:conversion_rate] %>% | -
| Mese | -Won | -Lost | +Fonte | +Lead | +Opp. | +Clienti | +Conv. |
|---|---|---|---|---|---|---|---|
| <%= l(row[:month], format: "%B %Y") %> | -<%= row[:won] %> | -<%= row[:lost] %> | +<%= row[:label] %> | +<%= row[:leads] %> | +<%= row[:opportunities] %> | +<%= row[:customers] %> | +<%= row[:conversion_rate] %>% |
| Mese | +Won | +Lost | +
|---|---|---|
| <%= l(row[:month], format: "%B %Y") %> | +<%= row[:won] %> | +<%= row[:lost] %> | +
| Utente | -Aperte | -Valore aperte | -Vinte | -Valore vinte | -||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <%= row[:user].full_name %> | -<%= row[:open_count] %> | -<%= format_money(row[:open_value]) %> | -<%= row[:won_count] %> | -<%= format_money(row[:won_value]) %> | +
| Utente | +Aperte | +Valore aperte | +Vinte | +Valore vinte |
|---|