Rende il CRM comodo da usare anche da cellulare.
CI / scan_ruby (push) Failing after 11m43s
CI / scan_js (push) Successful in 11m18s
CI / lint (push) Failing after 11m56s

Aggiunge navigazione mobile dedicata e trasforma le principali liste operative in layout più leggibili e facili da toccare.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 10:55:50 +02:00
co-authored by Cursor
parent d98c3e87a8
commit 0e8730b889
24 changed files with 683 additions and 295 deletions
+66 -41
View File
@@ -1,10 +1,10 @@
<div class="space-y-4">
<div class="flex flex-wrap items-center justify-between gap-3">
<h1 class="text-2xl font-semibold">Organizzazioni</h1>
<div class="flex gap-2">
<%= 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 %>
<h1 class="text-xl font-semibold md:text-2xl">Organizzazioni</h1>
<div class="flex w-full flex-wrap gap-2 sm:w-auto">
<%= 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" %>
</div>
</div>
@@ -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 %>
<label class="flex items-center gap-2 text-sm text-zinc-700 dark:text-zinc-300">
<label class="flex min-h-11 items-center gap-2 text-sm text-zinc-700 dark:text-zinc-300">
<%= check_box_tag :overdue_tasks, "1", params[:overdue_tasks] == "1" %>
Task scaduti
</label>
@@ -29,45 +29,70 @@
<%= select_tag :ab_variant, options_for_catalog(Catalog::AB_VARIANTS, params[:ab_variant]), include_blank: "Test A/B", class: input_class %>
<%= select_tag :sort, options_for_select([["Lista campagna", "lista"], ["Aggiornate", "updated"], ["Nome", "name"], ["Create", "created"]], params[:sort]), class: input_class %>
</div>
<div class="mt-3 flex gap-2">
<%= submit_tag "Filtra", class: btn_primary %>
<%= link_to "Reset", organizations_path, class: btn_secondary %>
<div class="mt-3 flex flex-wrap gap-2">
<%= submit_tag "Filtra", class: "#{btn_primary} flex-1 sm:flex-none" %>
<%= link_to "Reset", organizations_path, class: "#{btn_secondary} flex-1 sm:flex-none" %>
</div>
<% end %>
<div class="overflow-hidden rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
<table class="min-w-full divide-y divide-zinc-100 dark:divide-zinc-800 text-sm">
<thead class="bg-zinc-50 text-left text-xs uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
<tr>
<th class="px-4 py-3">N.</th>
<th class="px-4 py-3">Organizzazione</th>
<th class="px-4 py-3">Regione</th>
<th class="px-4 py-3">M/F</th>
<th class="px-4 py-3">Streaming</th>
<th class="px-4 py-3">Test</th>
<th class="px-4 py-3">Stage</th>
<th class="px-4 py-3">Invio</th>
</tr>
</thead>
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
<% @organizations.each do |org| %>
<tr class="hover:bg-zinc-50 dark:hover:bg-zinc-800/80">
<td class="px-4 py-3 tabular-nums text-zinc-500"><%= org.list_position || "—" %></td>
<td class="px-4 py-3">
<%= link_to org.name, org, class: "font-medium text-zinc-900 hover:underline dark:text-zinc-100" %>
<div class="text-xs text-slate-500"><%= org.email.presence || org.organization_type_label %></div>
</td>
<td class="px-4 py-3"><%= [org.region, org.province].compact_blank.join(" · ").presence || "—" %></td>
<td class="px-4 py-3"><%= org.team_gender_label.presence || "—" %></td>
<td class="px-4 py-3"><%= streaming_badge(org.streaming_status) || "—" %></td>
<% opp = org.campaign_opportunity(current_project) %>
<td class="px-4 py-3"><%= opp&.ab_variant.presence || "—" %></td>
<td class="px-4 py-3"><%= opp ? stage_badge(opp.pipeline_stage) : "—" %></td>
<td class="px-4 py-3"><%= opp&.send_status_label.presence || "—" %></td>
<div class="space-y-2 md:hidden">
<% @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 %>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<div class="font-semibold break-anywhere"><%= org.name %></div>
<div class="mt-0.5 text-xs text-zinc-500"><%= [org.list_position, org.region, org.province].compact_blank.join(" · ").presence || "—" %></div>
</div>
<%= opp ? stage_badge(opp.pipeline_stage) : nil %>
</div>
<div class="mt-2 flex flex-wrap items-center gap-2 text-xs text-zinc-500">
<%= streaming_badge(org.streaming_status) %>
<% if opp&.ab_variant.present? %><%= ab_variant_badge(opp.ab_variant) %><% end %>
<span><%= opp&.send_status_label.presence || "Invio n/d" %></span>
</div>
<% end %>
<% end %>
<% if @organizations.blank? %>
<p class="<%= card_class %> p-6 text-sm text-zinc-500">Nessuna organizzazione.</p>
<% end %>
</div>
<div class="hidden <%= card_class %> md:block">
<div class="<%= table_wrap_class %>">
<table class="min-w-full divide-y divide-zinc-100 dark:divide-zinc-800 text-sm">
<thead class="bg-zinc-50 text-left text-xs uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
<tr>
<th class="px-4 py-3">N.</th>
<th class="px-4 py-3">Organizzazione</th>
<th class="px-4 py-3">Regione</th>
<th class="px-4 py-3">M/F</th>
<th class="px-4 py-3">Streaming</th>
<th class="px-4 py-3">Test</th>
<th class="px-4 py-3">Stage</th>
<th class="px-4 py-3">Invio</th>
</tr>
<% end %>
</tbody>
</table>
</thead>
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
<% @organizations.each do |org| %>
<tr class="hover:bg-zinc-50 dark:hover:bg-zinc-800/80">
<td class="px-4 py-3 tabular-nums text-zinc-500"><%= org.list_position || "—" %></td>
<td class="px-4 py-3">
<%= link_to org.name, org, class: "font-medium text-zinc-900 hover:underline dark:text-zinc-100" %>
<div class="text-xs text-slate-500"><%= org.email.presence || org.organization_type_label %></div>
</td>
<td class="px-4 py-3"><%= [org.region, org.province].compact_blank.join(" · ").presence || "—" %></td>
<td class="px-4 py-3"><%= org.team_gender_label.presence || "—" %></td>
<td class="px-4 py-3"><%= streaming_badge(org.streaming_status) || "—" %></td>
<% opp = org.campaign_opportunity(current_project) %>
<td class="px-4 py-3"><%= opp&.ab_variant.presence || "—" %></td>
<td class="px-4 py-3"><%= opp ? stage_badge(opp.pipeline_stage) : "—" %></td>
<td class="px-4 py-3"><%= opp&.send_status_label.presence || "—" %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<%== pagy_nav(@pagy) if @pagy.pages > 1 %>
</div>
+12 -12
View File
@@ -2,7 +2,7 @@
<div class="rounded-2xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100 p-5">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<h1 class="text-2xl font-semibold tracking-tight"><%= @organization.name %></h1>
<h1 class="break-anywhere text-xl font-semibold tracking-tight md:text-2xl"><%= @organization.name %></h1>
<div class="mt-2 flex flex-wrap items-center gap-2">
<%= status_badge(@organization.status) %>
<% stage = @organization.primary_pipeline_stage(current_project) %>
@@ -12,7 +12,7 @@
<span class="text-sm text-slate-500">Progetti: <%= @organization.projects.ordered.map(&:name).join(", ").presence || "—" %></span>
</div>
</div>
<div class="flex gap-2">
<div class="flex w-full flex-wrap gap-2 sm:w-auto">
<%= link_to "Modifica", edit_organization_path(@organization), class: btn_secondary %>
<%= button_to "Elimina", @organization, method: :delete, form: { data: { turbo_confirm: "Eliminare?" } }, class: btn_danger %>
</div>
@@ -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 %>
</div>
@@ -71,8 +71,8 @@
<div><dt class="text-slate-500">Sport</dt><dd class="font-medium"><%= [@organization.sport.presence, @organization.team_gender_label.presence].compact.join(" · ").presence || "—" %></dd></div>
<div><dt class="text-slate-500">Località</dt><dd class="font-medium"><%= [@organization.city, @organization.province, @organization.region, @organization.country].compact_blank.join(", ").presence || "—" %></dd></div>
<div><dt class="text-slate-500">Lista campagna</dt><dd class="font-medium"><%= @organization.list_position || "—" %></dd></div>
<div><dt class="text-slate-500">Email</dt><dd class="font-medium"><%= @organization.email.presence || "—" %></dd></div>
<div><dt class="text-slate-500">Telefono</dt><dd class="font-medium"><%= @organization.phone.presence || "—" %></dd></div>
<div><dt class="text-slate-500">Email</dt><dd class="font-medium break-anywhere"><%= mailto_link(@organization.email, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
<div><dt class="text-slate-500">Telefono</dt><dd class="font-medium"><%= tel_link(@organization.phone, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
<div><dt class="text-slate-500">Sito / profilo</dt><dd class="font-medium"><%= external_link(@organization.website, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
<div><dt class="text-slate-500">Fonte ricerca</dt><dd class="font-medium"><%= external_link(@organization.source_url, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
<div><dt class="text-slate-500">Streaming rilevato</dt><dd class="font-medium"><%= streaming_badge(@organization.streaming_status) || "—" %></dd></div>
@@ -85,7 +85,7 @@
<section class="rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100 p-5">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">Contatti</h2>
<%= 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" %>
</div>
<div class="mt-3 divide-y divide-zinc-100 dark:divide-zinc-800">
<% @contacts.each do |contact| %>
@@ -95,7 +95,7 @@
<%= contact.full_name %>
<% if contact.primary_contact? %><span class="ml-1 rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-emerald-800">Principale</span><% end %>
</div>
<div class="text-sm text-slate-500"><%= contact.role %> · <%= contact.email %> · <%= contact.phone.presence || contact.mobile %></div>
<div class="break-anywhere text-sm text-slate-500"><%= [contact.role, contact.email, contact.phone.presence || contact.mobile].compact_blank.join(" · ") %></div>
</div>
<%= link_to "Modifica", edit_contact_path(contact), class: "text-sm text-zinc-600 hover:underline dark:text-zinc-300" %>
</div>
@@ -107,7 +107,7 @@
<section class="rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100 p-5">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">Opportunità</h2>
<%= 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" %>
</div>
<div class="mt-3 space-y-3">
<% @opportunities.each do |opp| %>
@@ -142,7 +142,7 @@
<section class="rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100 p-5">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">Task</h2>
<%= 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" %>
</div>
<h3 class="mt-3 text-sm font-semibold text-zinc-600 dark:text-zinc-400">Pendenti</h3>
<%= render "shared/task_list", tasks: @pending_tasks %>