Migliora pipeline e form per l'uso da cellulare.
Rende la pipeline verticale su mobile e semplifica i form lunghi con sezioni, tap target più grandi e azioni più facili da usare in movimento. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,51 +1,98 @@
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h1 class="text-2xl font-semibold">Pipeline</h1>
|
||||
<p class="text-sm text-slate-500">Scorri orizzontalmente · trascina le card o cambia stage dal menu</p>
|
||||
<h1 class="text-xl font-semibold md:text-2xl">Pipeline</h1>
|
||||
<p class="text-sm text-slate-500">Su cellulare la vedi per stage in verticale. Su desktop resta trascinabile.</p>
|
||||
</div>
|
||||
|
||||
<div class="kanban-board -mx-4 overflow-x-auto overscroll-x-contain px-4 pb-3 md:-mx-6 md:px-6"
|
||||
data-controller="kanban"
|
||||
data-kanban-project-code-value="<%= current_project.code %>">
|
||||
<div class="flex w-max min-w-full gap-3">
|
||||
<% Catalog::PIPELINE_ORDER.each do |stage| %>
|
||||
<div class="flex w-72 shrink-0 flex-col rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900"
|
||||
data-action="dragover->kanban#dragover dragleave->kanban#dragleave drop->kanban#drop"
|
||||
data-stage="<%= stage %>">
|
||||
<div class="sticky top-0 z-10 rounded-t-xl border-b border-zinc-200 bg-zinc-100 px-3 py-3 dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100"><%= Catalog.label_for(Catalog::PIPELINE_STAGES, stage) %></div>
|
||||
<div class="text-xs text-zinc-500"><%= @opportunities_by_stage[stage].size %> opportunità</div>
|
||||
<div class="space-y-4 md:hidden">
|
||||
<% Catalog::PIPELINE_ORDER.each do |stage| %>
|
||||
<% opportunities = @opportunities_by_stage[stage] %>
|
||||
<section class="<%= card_class %> overflow-hidden">
|
||||
<div class="border-b border-zinc-100 bg-zinc-50 px-4 py-3 dark:border-zinc-800 dark:bg-zinc-800/60">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100"><%= Catalog.label_for(Catalog::PIPELINE_STAGES, stage) %></h2>
|
||||
<span class="text-xs text-zinc-500"><%= opportunities.size %> opportunità</span>
|
||||
</div>
|
||||
<div class="max-h-[calc(100vh-14rem)] space-y-2 overflow-y-auto p-2">
|
||||
<% @opportunities_by_stage[stage].each do |opp| %>
|
||||
<div class="cursor-grab rounded-lg border border-zinc-200 bg-white p-3 shadow-sm dark:border-zinc-700 dark:bg-zinc-800 dark:shadow-none active:cursor-grabbing"
|
||||
draggable="true"
|
||||
data-action="dragstart->kanban#dragstart"
|
||||
data-opportunity-id="<%= opp.id %>">
|
||||
</div>
|
||||
|
||||
<% if opportunities.any? %>
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% opportunities.each do |opp| %>
|
||||
<% next_task = opp.next_pending_task || opp.organization.next_pending_task %>
|
||||
<div class="p-4">
|
||||
<%= link_to opp.organization.name, opp.organization, class: "font-medium text-zinc-900 hover:underline dark:text-zinc-50", data: { turbo: false } %>
|
||||
<div class="mt-0.5 text-sm text-zinc-600 dark:text-zinc-300"><%= opp.name %></div>
|
||||
<div class="mt-1 text-sm font-semibold tabular-nums text-zinc-900 dark:text-zinc-50"><%= format_money(opp.estimated_value) %></div>
|
||||
<div class="mt-1 text-xs text-zinc-500">
|
||||
<div class="mt-2 text-sm font-semibold tabular-nums text-zinc-900 dark:text-zinc-50"><%= format_money(opp.estimated_value) %></div>
|
||||
<div class="mt-1 text-xs text-zinc-500 break-anywhere">
|
||||
<%= opp.organization.primary_contact&.full_name || opp.organization.contacts.first&.full_name || "—" %>
|
||||
</div>
|
||||
<% next_task = opp.next_pending_task || opp.organization.next_pending_task %>
|
||||
<div class="mt-1 text-xs <%= next_task&.overdue? ? 'font-medium text-rose-700 dark:text-rose-400' : 'text-zinc-500' %>">
|
||||
<%= next_task ? "Next: #{next_task.title}" : "Nessuna next action" %>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-zinc-500">
|
||||
Ultima attività: <%= opp.organization.days_since_last_activity ? "#{opp.organization.days_since_last_activity}gg fa" : "—" %>
|
||||
</div>
|
||||
<%= form_with url: update_stage_opportunity_path(opp), method: :patch, class: "mt-2" do %>
|
||||
<%= select_tag :pipeline_stage, options_for_catalog(Catalog::PIPELINE_STAGES, opp.pipeline_stage), class: "#{input_class} text-xs py-1", onchange: "this.form.requestSubmit()" %>
|
||||
<%= form_with url: update_stage_opportunity_path(opp), method: :patch, class: "mt-3 space-y-2" do %>
|
||||
<%= select_tag :pipeline_stage, options_for_catalog(Catalog::PIPELINE_STAGES, opp.pipeline_stage), class: input_class, onchange: "this.form.requestSubmit()" %>
|
||||
<% if stage != "lost" %>
|
||||
<%= select_tag :lost_reason, options_for_catalog(Catalog::LOST_REASONS), include_blank: "Motivo se perso", class: "#{input_class} mt-1 text-xs py-1" %>
|
||||
<%= select_tag :lost_reason, options_for_catalog(Catalog::LOST_REASONS), include_blank: "Motivo se perso", class: input_class %>
|
||||
<% end %>
|
||||
<%= link_to "Apri scheda", opp.organization, class: "#{btn_secondary} w-full" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="p-4 text-sm text-zinc-500">Nessuna opportunità in questo stage.</p>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:block">
|
||||
<div class="kanban-board -mx-4 overflow-x-auto overscroll-x-contain px-4 pb-3 md:-mx-6 md:px-6"
|
||||
data-controller="kanban"
|
||||
data-kanban-project-code-value="<%= current_project.code %>">
|
||||
<div class="flex w-max min-w-full gap-3">
|
||||
<% Catalog::PIPELINE_ORDER.each do |stage| %>
|
||||
<div class="flex w-72 shrink-0 flex-col rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900"
|
||||
data-action="dragover->kanban#dragover dragleave->kanban#dragleave drop->kanban#drop"
|
||||
data-stage="<%= stage %>">
|
||||
<div class="sticky top-0 z-10 rounded-t-xl border-b border-zinc-200 bg-zinc-100 px-3 py-3 dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100"><%= Catalog.label_for(Catalog::PIPELINE_STAGES, stage) %></div>
|
||||
<div class="text-xs text-zinc-500"><%= @opportunities_by_stage[stage].size %> opportunità</div>
|
||||
</div>
|
||||
<div class="max-h-[calc(100vh-14rem)] space-y-2 overflow-y-auto p-2">
|
||||
<% @opportunities_by_stage[stage].each do |opp| %>
|
||||
<div class="cursor-grab rounded-lg border border-zinc-200 bg-white p-3 shadow-sm dark:border-zinc-700 dark:bg-zinc-800 dark:shadow-none active:cursor-grabbing"
|
||||
draggable="true"
|
||||
data-action="dragstart->kanban#dragstart"
|
||||
data-opportunity-id="<%= opp.id %>">
|
||||
<%= link_to opp.organization.name, opp.organization, class: "font-medium text-zinc-900 hover:underline dark:text-zinc-50", data: { turbo: false } %>
|
||||
<div class="mt-0.5 text-sm text-zinc-600 dark:text-zinc-300"><%= opp.name %></div>
|
||||
<div class="mt-1 text-sm font-semibold tabular-nums text-zinc-900 dark:text-zinc-50"><%= format_money(opp.estimated_value) %></div>
|
||||
<div class="mt-1 text-xs text-zinc-500">
|
||||
<%= opp.organization.primary_contact&.full_name || opp.organization.contacts.first&.full_name || "—" %>
|
||||
</div>
|
||||
<% next_task = opp.next_pending_task || opp.organization.next_pending_task %>
|
||||
<div class="mt-1 text-xs <%= next_task&.overdue? ? 'font-medium text-rose-700 dark:text-rose-400' : 'text-zinc-500' %>">
|
||||
<%= next_task ? "Next: #{next_task.title}" : "Nessuna next action" %>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-zinc-500">
|
||||
Ultima attività: <%= opp.organization.days_since_last_activity ? "#{opp.organization.days_since_last_activity}gg fa" : "—" %>
|
||||
</div>
|
||||
<%= form_with url: update_stage_opportunity_path(opp), method: :patch, class: "mt-2" do %>
|
||||
<%= select_tag :pipeline_stage, options_for_catalog(Catalog::PIPELINE_STAGES, opp.pipeline_stage), class: "#{input_class} text-xs py-1", onchange: "this.form.requestSubmit()" %>
|
||||
<% if stage != "lost" %>
|
||||
<%= select_tag :lost_reason, options_for_catalog(Catalog::LOST_REASONS), include_blank: "Motivo se perso", class: "#{input_class} mt-1 text-xs py-1" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user