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 @@
-
-

Contatti

-
- <%= link_to "Export CSV", contacts_path(format: :csv), class: btn_secondary %> - <%= link_to "Nuovo", new_contact_path, class: btn_primary %> +
+

Contatti

+
+ <%= link_to "Export CSV", contacts_path(format: :csv), class: "#{btn_secondary} flex-1 sm:flex-none" %> + <%= link_to "Nuovo", new_contact_path, class: "#{btn_primary} flex-1 sm:flex-none" %>
- <%= form_with url: contacts_path, method: :get, class: "flex gap-2" do %> + <%= form_with url: contacts_path, method: :get, class: "flex flex-col gap-2 sm:flex-row" do %> <%= text_field_tag :q, params[:q], placeholder: "Cerca…", class: input_class %> <%= submit_tag "Cerca", class: btn_primary %> <% end %> -
- - - - - - <% @contacts.each do |c| %> - - - - - - + +
+ <% @contacts.each do |c| %> + <%= link_to c.organization, class: "#{card_class} block p-4 active:bg-zinc-50 dark:active:bg-zinc-800" do %> +
<%= c.full_name %><% if c.primary_contact? %> ★<% end %>
+
<%= c.organization.name %>
+
<%= c.email %>
+ <% if c.phone.present? || c.mobile.present? %> +
<%= c.phone.presence || c.mobile %>
<% end %> -
-
NomeOrganizzazioneEmailTelefono
<%= 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 %>
+ <% end %> + <% end %> + <% if @contacts.blank? %> +

Nessun contatto.

+ <% end %> +
+ + <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
diff --git a/app/views/contacts/show.html.erb b/app/views/contacts/show.html.erb index e1c229d..fa96c9d 100644 --- a/app/views/contacts/show.html.erb +++ b/app/views/contacts/show.html.erb @@ -1,11 +1,30 @@
-

<%= @contact.full_name %>

-
-

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" %> +
+
+

<%= @contact.full_name %>

+

<%= link_to @contact.organization.name, @contact.organization, class: "hover:underline" %>

+
+ <%= link_to "Modifica", edit_contact_path(@contact), class: "#{btn_secondary} w-full sm:w-auto" %> +
+ +
+
+
+
Ruolo
+
<%= @contact.role.presence || "—" %>
+
+
+
Email
+
<%= mailto_link(@contact.email, class: "text-sky-700 hover:underline dark:text-sky-400") %>
+
+
+
Telefono
+
<%= tel_link(@contact.phone, class: "text-sky-700 hover:underline dark:text-sky-400") %>
+
+
+
Cellulare
+
<%= tel_link(@contact.mobile, class: "text-sky-700 hover:underline dark:text-sky-400") %>
+
+
diff --git a/app/views/dashboard/show.html.erb b/app/views/dashboard/show.html.erb index e129c43..3492dca 100644 --- a/app/views/dashboard/show.html.erb +++ b/app/views/dashboard/show.html.erb @@ -1,10 +1,10 @@
-

Dashboard · <%= current_project.name %>

+

Dashboard · <%= current_project.name %>

Cosa devo fare oggi per trasformare i prospect in clienti?

- <%= link_to "Apri pagina Oggi →", today_path, class: btn_primary %> + <%= link_to "Apri pagina Oggi →", today_path, class: "#{btn_primary} w-full sm:w-auto" %>
<% if @goal %> @@ -36,7 +36,7 @@
-
+
<% [ ["Prospect totali", @metrics.prospect_count], ["Contattati", @stage_counts["contacted"]], @@ -76,7 +76,7 @@

KPI processo

-
+
Giorni medi contatto → WON
<%= @metrics.avg_days_to_won || "—" %>
Giorni medi dall'ultima attività
<%= @metrics.avg_days_since_last_activity || "—" %>
Prospect senza attività > 7gg
<%= @metrics.prospects_without_activity_over_7_days %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 228d531..e44f2b0 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -11,8 +11,10 @@ })(); <%= page_title %><% if current_project %> · <%= current_project.name %><% end %> · <%= app_name %> - + + + <%= csrf_meta_tags %> <%= csp_meta_tag %> @@ -60,63 +62,58 @@
-
-
-
- <%= link_to root_path, class: "flex items-center gap-2 font-semibold tracking-tight" do %> +
+
+
+ <%= link_to root_path, class: "flex max-w-[9rem] items-center gap-2 font-semibold tracking-tight" do %> <%= render "shared/logo", variant: :mark, size: :xs %> - <%= current_project.name %> + <%= current_project.name %> <% end %> - <%= link_to "Progetti", root_path, class: "text-xs text-zinc-500" %>
- <%= form_with url: search_path, method: :get, class: "flex min-w-0 flex-1" do %> - <%= text_field_tag :q, params[:q], placeholder: "Cerca in #{current_project.name}…", class: input_class %> + <%= form_with url: search_path, method: :get, class: "min-w-0 flex-1" do %> + <%= text_field_tag :q, params[:q], placeholder: "Cerca…", class: "#{input_class} min-h-10 py-2", aria: { label: "Cerca in #{current_project.name}" } %> <% end %> -
- +
+
<%= render "shared/theme_toggle" %> <%= render "shared/user_menu" %>
-
-
+
<%= render "shared/flash" %>
<%= yield %>
+
+ <%= render "shared/mobile_bottom_nav" %> + <%= render "shared/mobile_more_sheet" %> +
<% elsif logged_in? %>
-
-
-
+
+
+
<%= link_to root_path, class: "text-zinc-900 dark:text-zinc-100" do %> <%= render "shared/logo", subtitle: (@page_title || "Seleziona un'istanza progetto") %> <% end %>
-
+
<% if current_user.admin? %> - <%= link_to "Impostazioni", admin_path, class: btn_secondary %> + <%= link_to "Impostazioni", admin_path, class: "#{btn_secondary} hidden sm:inline-flex" %> <% end %> <%= render "shared/theme_toggle" %> <%= render "shared/user_menu" %> @@ -132,7 +129,7 @@ <% end %>
<% else %> -
+
<%= render "shared/theme_toggle" %>
diff --git a/app/views/mail_identities/index.html.erb b/app/views/mail_identities/index.html.erb index ebc94e4..bc1b607 100644 --- a/app/views/mail_identities/index.html.erb +++ b/app/views/mail_identities/index.html.erb @@ -1,20 +1,38 @@
-

Account email / SMTP

+

Account email / SMTP

<%= link_to "Impostazioni", admin_path, class: "hover:underline" %> · da questi indirizzi partono le campagne. Le password SMTP sono cifrate.

- <%= link_to "Nuovo account", new_mail_identity_path, class: btn_primary %> + <%= link_to "Nuovo account", new_mail_identity_path, class: "#{btn_primary} w-full sm:w-auto" %>
-
+
<% if @mail_identities.empty? %>

Nessun account. Crea il mittente con host, porta e credenziali SMTP.

<% else %> - +
+ <% @mail_identities.each do |identity| %> +
"> +
+
+
<%= identity.name %>
+
<%= identity.from_name %>
+
<%= identity.from_email %>
+
+ <%= link_to "Modifica", edit_mail_identity_path(identity), class: "text-sm text-zinc-700 hover:underline dark:text-zinc-300" %> +
+
<%= identity.smtp_host %>:<%= identity.smtp_port %> · <%= Catalog.label_for(Catalog::MAIL_ENCRYPTIONS, identity.encryption) %>
+
<%= identity.active? ? "Attivo" : "Disattivo" %>
+
+ <% end %> +
+
@@ -44,6 +62,8 @@ <% end %>
Nome
+
+
<% end %>
diff --git a/app/views/mail_templates/index.html.erb b/app/views/mail_templates/index.html.erb index 36f2bf0..d27d875 100644 --- a/app/views/mail_templates/index.html.erb +++ b/app/views/mail_templates/index.html.erb @@ -1,20 +1,36 @@
-

Template email

+

Template email

<%= link_to "Invii", dashboard_mailings_path, class: "hover:underline" %> · HTML riutilizzabile con variabili della scheda cliente.

- <%= link_to "Nuovo template", new_mail_template_path, class: btn_primary %> + <%= link_to "Nuovo template", new_mail_template_path, class: "#{btn_primary} w-full sm:w-auto" %>
-
+
<% if @mail_templates.empty? %>

Nessun template. Creane uno e usalo come base per gli invii.

<% else %> - +
+ <% @mail_templates.each do |template| %> +
+
+
+
<%= template.name %>
+
<%= template.subject %>
+
<%= template.project_id? ? current_project.name : "Tutti i progetti" %>
+
+ <%= link_to "Modifica", edit_mail_template_path(template), class: "text-sm text-zinc-700 hover:underline dark:text-zinc-300" %> +
+
+ <% end %> +
+
@@ -36,6 +52,8 @@ <% end %>
Nome
+
+
<% end %>
diff --git a/app/views/mailings/dashboard.html.erb b/app/views/mailings/dashboard.html.erb index 80c7406..f5e46dc 100644 --- a/app/views/mailings/dashboard.html.erb +++ b/app/views/mailings/dashboard.html.erb @@ -2,15 +2,15 @@ <%= tag.div class: "space-y-6", data: (refresh ? { controller: "auto-reload", auto_reload_interval_value: 10_000 } : {}) do %>
-

Invii email

+

Invii email

Stato delle campagne, prove e invii di massa scaglionati.

-
- <%= link_to "Template", mail_templates_path, class: btn_secondary %> +
+ <%= link_to "Template", mail_templates_path, class: "#{btn_secondary} flex-1 sm:flex-none" %> <% if current_user.admin? %> - <%= link_to "Account SMTP", mail_identities_path, class: btn_secondary %> + <%= link_to "Account SMTP", mail_identities_path, class: "#{btn_secondary} flex-1 sm:flex-none" %> <% end %> - <%= link_to "Nuovo invio", new_mailing_path, class: btn_primary %> + <%= link_to "Nuovo invio", new_mailing_path, class: "#{btn_primary} flex-1 sm:flex-none" %>
@@ -25,7 +25,7 @@
<% end %> -
+
In corso
<%= @active_mailings.size %>
@@ -56,11 +56,33 @@
<% end %> -
+
<% if @mailings.empty? %>

Nessun invio. Crea una bozza, fai una prova con dati manuali e poi autorizza l’invio di massa.

<% else %> - +
+ <% @mailings.each do |mailing| %> + <%= link_to mailing, class: "block p-4 active:bg-zinc-50 dark:active:bg-zinc-800" do %> +
+
+
<%= mailing.name %>
+
<%= mailing.mail_identity.from_email %>
+
+ <%= mailing_status_badge(mailing.status) %> +
+
<%= progress_bar(mailing.progress_percentage) %>
+
+ <%= mailing.sent_count %> inviate · <%= mailing.pending_count %> in coda + <% if mailing.sending? && mailing.next_send_at.present? && mailing.next_send_at > Time.current %> + · Riparte <%= l(mailing.next_send_at, format: :short) %> + <% end %> +
+ <% end %> + <% end %> +
+
@@ -107,7 +129,9 @@ <% end %> -
Invio
+ +
+
<% end %>
<% end %> diff --git a/app/views/mailings/show.html.erb b/app/views/mailings/show.html.erb index a1ffeff..0b4dba6 100644 --- a/app/views/mailings/show.html.erb +++ b/app/views/mailings/show.html.erb @@ -1,7 +1,7 @@ <%= tag.div class: "space-y-6", data: (@mailing.sending? ? { controller: "auto-reload", auto_reload_interval_value: 10_000 } : {}) do %>
-
-

<%= @mailing.name %>

+
+

<%= @mailing.name %>

<%= mailing_status_badge(@mailing.status) %> · <%= @mailing.audience_label %> @@ -25,8 +25,8 @@

<% end %>
-
- <%= link_to "Cruscotto invii", dashboard_mailings_path, class: btn_secondary %> +
+ <%= link_to "Cruscotto invii", dashboard_mailings_path, class: "#{btn_secondary} flex-1 sm:flex-none" %> <% if @mailing.editable? %> <%= link_to "Modifica contenuto", edit_mailing_path(@mailing), class: btn_secondary %> <%= button_to "Aggiorna lista", refresh_recipients_mailing_path(@mailing), method: :post, class: btn_secondary %> @@ -42,7 +42,7 @@ <%= render "mailings/progress_card", mailing: @mailing %> <% end %> -
+
Da inviare
<%= @mailing.pending_count %>
@@ -103,68 +103,113 @@
<%= form_with url: update_recipients_mailing_path(@mailing), method: :patch do %> -
- - - +
+ <% if @mailing.editable? %> + + <% end %> + +
+ <% @recipients.each do |recipient| %> +
"> <% if @mailing.editable? %> -
+
+ <% if recipient.email_ok? %> + <%= check_box_tag "pending_ids[]", recipient.id, recipient.pending?, + id: "recipient_#{recipient.id}", + class: "size-5", + data: { check_all_target: "checkbox" } %> + <% else %> + + <% end %> +
<% end %> - - - <% if @mailing.ab_test? %> - - <% end %> - - - - - - <% @recipients.each do |recipient| %> - "> - <% if @mailing.editable? %> - - <% end %> - - - <% if @mailing.ab_test? %> - + + <% if recipient.skip_reason.present? && !recipient.pending? %> +
<%= recipient.skip_reason %>
<% end %> - - + + + + + <% @recipients.each do |recipient| %> + "> + + + <% if @mailing.ab_test? %> + + <% end %> + + + + <% end %> + +
- - OrganizzazioneEmailTestStato
- <% if recipient.email_ok? %> - <%= check_box_tag "pending_ids[]", recipient.id, recipient.pending?, - id: "recipient_#{recipient.id}", - data: { check_all_target: "checkbox" } %> - <% else %> - - <% 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 %> -
<%= ab_variant_badge(recipient.ab_variant) || "—" %><%= 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 %> +
<%= ab_variant_badge(recipient.ab_variant) || "—" %><%= 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 %> +
+
+
<% if @mailing.editable? %> diff --git a/app/views/opportunities/index.html.erb b/app/views/opportunities/index.html.erb index 215271d..4537323 100644 --- a/app/views/opportunities/index.html.erb +++ b/app/views/opportunities/index.html.erb @@ -1,12 +1,27 @@
-
-

Opportunità

-
- <%= link_to "Export CSV", opportunities_path(format: :csv), class: btn_secondary %> - <%= link_to "Nuova", new_opportunity_path, class: btn_primary %> +
+

Opportunità

+
+ <%= link_to "Export CSV", opportunities_path(format: :csv), class: "#{btn_secondary} flex-1 sm:flex-none" %> + <%= link_to "Nuova", new_opportunity_path, class: "#{btn_primary} flex-1 sm:flex-none" %>
-
+
+ <% @opportunities.each do |opp| %> + <%= link_to opp.organization, class: "#{card_class} block p-4 active:bg-zinc-50 dark:active:bg-zinc-800" do %> +
+
+
<%= opp.organization.name %>
+
<%= opp.name %>
+
+ <%= stage_badge(opp.pipeline_stage) %> +
+
<%= format_money(opp.estimated_value) %> · <%= opp.assigned_user&.full_name || "—" %>
+ <% end %> + <% end %> +
+ <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb index 23898b7..22e69eb 100644 --- a/app/views/organizations/index.html.erb +++ b/app/views/organizations/index.html.erb @@ -1,10 +1,10 @@
-

Organizzazioni

-
- <%= link_to "Export CSV", organizations_path(request.query_parameters.merge(format: :csv)), class: btn_secondary %> - <%= link_to "Import CSV", new_import_path, class: btn_secondary %> - <%= link_to "Nuova", new_organization_path, class: btn_primary %> +

Organizzazioni

+
+ <%= link_to "Export CSV", organizations_path(request.query_parameters.merge(format: :csv)), class: "#{btn_secondary} flex-1 sm:flex-none" %> + <%= link_to "Import CSV", new_import_path, class: "#{btn_secondary} flex-1 sm:flex-none" %> + <%= link_to "Nuova", new_organization_path, class: "#{btn_primary} flex-1 sm:flex-none" %>
@@ -20,7 +20,7 @@ <%= text_field_tag :country, params[:country], placeholder: "Paese", class: input_class %> <%= text_field_tag :region, params[:region], placeholder: "Regione", class: input_class %> <%= select_tag :customer, options_for_select([["Cliente/non cliente", ""], ["Solo clienti", "yes"], ["Non clienti", "no"]], params[:customer]), class: input_class %> -
-
- <%= submit_tag "Filtra", class: btn_primary %> - <%= link_to "Reset", organizations_path, class: btn_secondary %> +
+ <%= submit_tag "Filtra", class: "#{btn_primary} flex-1 sm:flex-none" %> + <%= link_to "Reset", organizations_path, class: "#{btn_secondary} flex-1 sm:flex-none" %>
<% end %> -
- - - - - - - - - - - - - - - <% @organizations.each do |org| %> - - - - - - - <% opp = org.campaign_opportunity(current_project) %> - - - +
+ <% @organizations.each do |org| %> + <% opp = org.campaign_opportunity(current_project) %> + <%= link_to org, class: "#{card_class} block p-4 active:bg-zinc-50 dark:active:bg-zinc-800" do %> +
+
+
<%= org.name %>
+
<%= [org.list_position, org.region, org.province].compact_blank.join(" · ").presence || "—" %>
+
+ <%= opp ? stage_badge(opp.pipeline_stage) : nil %> +
+
+ <%= streaming_badge(org.streaming_status) %> + <% if opp&.ab_variant.present? %><%= ab_variant_badge(opp.ab_variant) %><% end %> + <%= opp&.send_status_label.presence || "Invio n/d" %> +
+ <% end %> + <% end %> + <% if @organizations.blank? %> +

Nessuna organizzazione.

+ <% end %> +
+ +
N.OrganizzazioneRegioneM/FStreamingTestStageInvio
<%= 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&.ab_variant.presence || "—" %><%= opp ? stage_badge(opp.pipeline_stage) : "—" %><%= opp&.send_status_label.presence || "—" %>
+ + + + + + + + + + - <% end %> - -
N.OrganizzazioneRegioneM/FStreamingTestStageInvio
+ + + <% @organizations.each do |org| %> + + <%= 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 || "—" %> + + <% end %> + + +
<%== pagy_nav(@pagy) if @pagy.pages > 1 %>
diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index 9819c54..4c77a3f 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -2,7 +2,7 @@
-

<%= @organization.name %>

+

<%= @organization.name %>

<%= status_badge(@organization.status) %> <% stage = @organization.primary_pipeline_stage(current_project) %> @@ -12,7 +12,7 @@ Progetti: <%= @organization.projects.ordered.map(&:name).join(", ").presence || "—" %>
-
+
<%= link_to "Modifica", edit_organization_path(@organization), class: btn_secondary %> <%= button_to "Elimina", @organization, method: :delete, form: { data: { turbo_confirm: "Eliminare?" } }, class: btn_danger %>
@@ -31,17 +31,17 @@ <%= hidden_field_tag "activity[activity_type]", type %> <%= hidden_field_tag "activity[subject]", subject %> <%= hidden_field_tag "activity[happened_at]", Time.current %> - <%= submit_tag label, class: "rounded-md border border-zinc-300 bg-zinc-50 px-3 py-1.5 text-xs 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" %> + <%= submit_tag label, class: chip_action_class %> <% end %> <% end %> - <%= link_to "Crea follow-up", new_task_path(organization_id: @organization.id, task_type: "follow_up"), class: "rounded-md border border-zinc-300 bg-zinc-50 px-3 py-1.5 text-xs 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" %> + <%= link_to "Crea follow-up", new_task_path(organization_id: @organization.id, task_type: "follow_up"), class: chip_action_class %> <% opp = @opportunities.open_stage.first || @opportunities.first %> <% if opp %> - <%= button_to "Segna come vinto", update_stage_opportunity_path(opp), method: :patch, params: { pipeline_stage: "won" }, class: "rounded-md bg-emerald-600 px-3 py-1.5 text-xs font-medium text-white" %> + <%= button_to "Segna come vinto", update_stage_opportunity_path(opp), method: :patch, params: { pipeline_stage: "won" }, class: "rounded-md bg-emerald-600 px-3 py-2 text-xs font-medium text-white" %> <%= form_with url: update_stage_opportunity_path(opp), method: :patch, class: "inline-flex items-center gap-1" do %> <%= hidden_field_tag :pipeline_stage, "lost" %> <%= select_tag :lost_reason, options_for_catalog(Catalog::LOST_REASONS), class: "rounded-md border border-zinc-300 bg-white px-2 py-1 text-xs dark:border-zinc-600 dark:bg-zinc-950 dark:text-zinc-100" %> - <%= submit_tag "Segna come perso", class: "rounded-md bg-rose-600 px-3 py-1.5 text-xs font-medium text-white" %> + <%= submit_tag "Segna come perso", class: "rounded-md bg-rose-600 px-3 py-2 text-xs font-medium text-white" %> <% end %> <% end %>
@@ -71,8 +71,8 @@
Sport
<%= [@organization.sport.presence, @organization.team_gender_label.presence].compact.join(" · ").presence || "—" %>
Località
<%= [@organization.city, @organization.province, @organization.region, @organization.country].compact_blank.join(", ").presence || "—" %>
Lista campagna
<%= @organization.list_position || "—" %>
-
Email
<%= @organization.email.presence || "—" %>
-
Telefono
<%= @organization.phone.presence || "—" %>
+
Email
<%= mailto_link(@organization.email, class: "text-sky-700 hover:underline dark:text-sky-400") %>
+
Telefono
<%= tel_link(@organization.phone, class: "text-sky-700 hover:underline dark:text-sky-400") %>
Sito / profilo
<%= external_link(@organization.website, class: "text-sky-700 hover:underline dark:text-sky-400") %>
Fonte ricerca
<%= external_link(@organization.source_url, class: "text-sky-700 hover:underline dark:text-sky-400") %>
Streaming rilevato
<%= streaming_badge(@organization.streaming_status) || "—" %>
@@ -85,7 +85,7 @@

Contatti

- <%= link_to "+ Contatto", new_contact_path(organization_id: @organization.id), class: "text-sm font-medium text-emerald-700 dark:text-emerald-400" %> + <%= link_to "+ Contatto", new_contact_path(organization_id: @organization.id), class: "text-sm font-medium text-emerald-700 hover:underline dark:text-emerald-400" %>
<% @contacts.each do |contact| %> @@ -95,7 +95,7 @@ <%= contact.full_name %> <% if contact.primary_contact? %>Principale<% end %>
-
<%= contact.role %> · <%= contact.email %> · <%= contact.phone.presence || contact.mobile %>
+
<%= [contact.role, contact.email, contact.phone.presence || contact.mobile].compact_blank.join(" · ") %>
<%= link_to "Modifica", edit_contact_path(contact), class: "text-sm text-zinc-600 hover:underline dark:text-zinc-300" %>
@@ -107,7 +107,7 @@

Opportunità

- <%= link_to "+ Opportunità", new_opportunity_path(organization_id: @organization.id), class: "text-sm font-medium text-emerald-700 dark:text-emerald-400" %> + <%= link_to "+ Opportunità", new_opportunity_path(organization_id: @organization.id), class: "text-sm font-medium text-emerald-700 hover:underline dark:text-emerald-400" %>
<% @opportunities.each do |opp| %> @@ -142,7 +142,7 @@

Task

- <%= link_to "+ Task", new_task_path(organization_id: @organization.id), class: "text-sm font-medium text-emerald-700 dark:text-emerald-400" %> + <%= link_to "+ Task", new_task_path(organization_id: @organization.id), class: "text-sm font-medium text-emerald-700 hover:underline dark:text-emerald-400" %>

Pendenti

<%= render "shared/task_list", tasks: @pending_tasks %> diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb index bba207d..86719d3 100644 --- a/app/views/reports/index.html.erb +++ b/app/views/reports/index.html.erb @@ -1,7 +1,7 @@
-

Report

+

Report

-
+

Funnel

@@ -28,97 +28,103 @@ -
+

Lead source

-
- - - - - - - - - - - <% @lead_sources.each do |row| %> - - - - - - - - <% end %> - -
FonteLeadOpp.ClientiConv.
<%= row[:label] %><%= row[:leads] %><%= row[:opportunities] %><%= row[:customers] %><%= row[:conversion_rate] %>%
-
- -
-
-

Won / Lost per mese

- +
+
- - - + + + + + - <% @won_lost.each do |row| %> + <% @lead_sources.each do |row| %> - - - + + + + + <% end %>
MeseWonLostFonteLeadOpp.ClientiConv.
<%= l(row[:month], format: "%B %Y") %><%= row[:won] %><%= row[:lost] %><%= row[:label] %><%= row[:leads] %><%= row[:opportunities] %><%= row[:customers] %><%= row[:conversion_rate] %>%
+
+
+ +
+
+

Won / Lost per mese

+
+ + + + + + + + + + <% @won_lost.each do |row| %> + + + + + + <% end %> + +
MeseWonLost
<%= l(row[:month], format: "%B %Y") %><%= row[:won] %><%= row[:lost] %>
+
-
+

Lost reasons

    <% @lost_reasons.each do |row| %> -
  • <%= row[:label] %><%= row[:count] %>
  • +
  • <%= row[:label] %><%= row[:count] %>
  • <% end %> <% if @lost_reasons.blank? %>
  • Nessun dato
  • <% end %>
-
+

Sales owner

- - - - - - - - - - - - <% @sales_owners.each do |row| %> - - - - - - +
+
UtenteAperteValore aperteVinteValore vinte
<%= row[:user].full_name %><%= row[:open_count] %><%= format_money(row[:open_value]) %><%= row[:won_count] %><%= format_money(row[:won_value]) %>
+ + + + + + + - <% end %> - -
UtenteAperteValore aperteVinteValore vinte
+ + + <% @sales_owners.each do |row| %> + + <%= row[:user].full_name %> + <%= row[:open_count] %> + <%= format_money(row[:open_value]) %> + <%= row[:won_count] %> + <%= format_money(row[:won_value]) %> + + <% end %> + + +
-
+

Revenue (WON per mese)

    <% @revenue.each do |row| %> -
  • <%= l(row[:month], format: "%B %Y") %><%= format_money(row[:value]) %>
  • +
  • <%= l(row[:month], format: "%B %Y") %><%= format_money(row[:value]) %>
  • <% end %> <% if @revenue.blank? %>
  • Nessun dato
  • <% end %>
diff --git a/app/views/shared/_mobile_bottom_nav.html.erb b/app/views/shared/_mobile_bottom_nav.html.erb new file mode 100644 index 0000000..5cff901 --- /dev/null +++ b/app/views/shared/_mobile_bottom_nav.html.erb @@ -0,0 +1,39 @@ + diff --git a/app/views/shared/_mobile_more_sheet.html.erb b/app/views/shared/_mobile_more_sheet.html.erb new file mode 100644 index 0000000..c575df5 --- /dev/null +++ b/app/views/shared/_mobile_more_sheet.html.erb @@ -0,0 +1,32 @@ + diff --git a/app/views/shared/_task_list.html.erb b/app/views/shared/_task_list.html.erb index af5d469..57547f2 100644 --- a/app/views/shared/_task_list.html.erb +++ b/app/views/shared/_task_list.html.erb @@ -1,7 +1,7 @@
<% tasks.each do |task| %> -
+
<%= link_to task.organization.name, task.organization, class: "font-medium text-zinc-900 hover:underline dark:text-zinc-100" %> @@ -14,7 +14,7 @@ <% if task.assigned_user %>· <%= task.assigned_user.full_name %><% end %>
-
+
<%= button_to "Completa", complete_task_path(task), method: :patch, class: "rounded-md bg-emerald-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-emerald-700" %> <%= link_to "Apri", task.organization, class: "rounded-md border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
diff --git a/app/views/shared/_theme_toggle.html.erb b/app/views/shared/_theme_toggle.html.erb index 26aef39..c70d562 100644 --- a/app/views/shared/_theme_toggle.html.erb +++ b/app/views/shared/_theme_toggle.html.erb @@ -1,7 +1,7 @@
diff --git a/app/views/tasks/index.html.erb b/app/views/tasks/index.html.erb index 78ee5bd..5c479bd 100644 --- a/app/views/tasks/index.html.erb +++ b/app/views/tasks/index.html.erb @@ -1,7 +1,7 @@
-
-

Task

- <%= link_to "Nuovo task", new_task_path, class: btn_primary %> +
+

Task

+ <%= link_to "Nuovo task", new_task_path, class: "#{btn_primary} w-full sm:w-auto" %>

In ritardo

diff --git a/app/views/today/show.html.erb b/app/views/today/show.html.erb index 7fb3706..f64fae8 100644 --- a/app/views/today/show.html.erb +++ b/app/views/today/show.html.erb @@ -1,6 +1,6 @@
-

Oggi

+

Oggi

<%= l(Time.zone.today, format: :long) %> · pagina operativa del mattino

@@ -44,7 +44,7 @@ <%= link_to org.name, org, class: "font-medium hover:underline" %>
<%= org.city %> · <%= org.lead_source_label %> · <%= org.assigned_user&.full_name || "Non assegnato" %>
- <%= link_to "Contatta", org, class: "#{btn_primary} px-3 py-1.5 text-xs" %> + <%= link_to "Contatta", org, class: "#{btn_primary} w-full sm:w-auto px-3 text-xs" %>
<% end %> <% if @new_prospects.blank? %>

Nessun nuovo prospect.

<% end %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index f04e23a..590f8ea 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,17 +1,41 @@
-

Utenti

+

Utenti

<%= link_to "Impostazioni", admin_path, class: "hover:underline" %> · account globali. Gli admin vedono tutti i progetti; gli utenti solo quelli abilitati.

- <%= link_to "Nuovo utente", new_user_path, class: btn_primary %> + <%= link_to "Nuovo utente", new_user_path, class: "#{btn_primary} w-full sm:w-auto" %>
-
- +
+
+ <% @users.each do |user| %> +
"> +
+
+
<%= user.full_name %><% if user == current_user %>(tu)<% end %>
+
<%= user.email %>
+
+ <%= link_to "Modifica", edit_user_path(user), class: "text-sm text-zinc-700 hover:underline dark:text-zinc-300" %> +
+
<%= user.admin? ? "Admin" : "Utente" %> · <%= user.active? ? "Attivo" : "Disabilitato" %>
+
+ <% if user.admin? %> + Tutti i progetti + <% else %> + <% names = user.user_projects.select(&:enabled?).filter_map { |up| up.project&.name } %> + <%= names.any? ? names.join(", ") : "—" %> + <% end %> +
+
+ <% end %> +
+
@@ -53,5 +77,7 @@ <% end %>
Nome
+
+
diff --git a/test/controllers/authentication_test.rb b/test/controllers/authentication_test.rb index 65ab7c7..cf2e971 100644 --- a/test/controllers/authentication_test.rb +++ b/test/controllers/authentication_test.rb @@ -44,6 +44,16 @@ class AuthenticationTest < ActionDispatch::IntegrationTest assert_redirected_to root_path end + test "project pages render mobile navigation shell" do + login_as users(:admin) + follow_redirect! if response.redirect? + + get project_root_path(project_code: "matchlivetv") + assert_response :success + assert_match(/mobile-bottom-nav/, response.body) + assert_match(/Altro/, response.body) + end + test "disabled user cannot login" do admin = users(:admin) User.create!(