Commit iniziale di eminuxCRM: CRM Rails con pipeline, campagne email e Docker.
Include autenticazione, progetti isolati, mail marketing HTML con SMTP, test A/B e editor WYSIWYG. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<% items = items.to_a %>
|
||||
<% empty_class = "mt-2 text-sm text-amber-900/75 dark:text-amber-100/75" %>
|
||||
<% if items.blank? %>
|
||||
<p class="<%= empty_class %>"><%= empty %></p>
|
||||
<% else %>
|
||||
<ul class="mt-2 space-y-1 text-sm">
|
||||
<% items.first(8).each do |item| %>
|
||||
<li>
|
||||
<% if block_given? %>
|
||||
<%= yield item %>
|
||||
<% else %>
|
||||
<%= link_to "#{item.organization.name} · #{item.name}", item.organization, class: "hover:underline" %>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if items.size > 8 %>
|
||||
<li class="text-xs text-amber-900/70 dark:text-amber-100/70">+ altre <%= items.size - 8 %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
@@ -0,0 +1,151 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Dashboard · <%= current_project.name %></h1>
|
||||
<p class="text-sm text-slate-500">Cosa devo fare oggi per trasformare i prospect in clienti?</p>
|
||||
</div>
|
||||
<%= link_to "Apri pagina Oggi →", today_path, class: btn_primary %>
|
||||
</div>
|
||||
|
||||
<% if @goal %>
|
||||
<section class="rounded-2xl border border-slate-200 bg-gradient-to-br from-slate-900 to-slate-800 p-6 text-white shadow-sm">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-slate-300"><%= @goal.name %></div>
|
||||
<div class="mt-2 flex flex-wrap items-end gap-4">
|
||||
<div class="text-4xl font-semibold tabular-nums"><%= @goal.current_value.to_i %> / <%= @goal.target_value.to_i %></div>
|
||||
<div class="pb-1 text-sm text-slate-300"><%= @goal.metric_label %> · <%= format_date(@goal.start_date) %> – <%= format_date(@goal.end_date) %></div>
|
||||
</div>
|
||||
<div class="mt-4"><%= progress_bar(@goal.progress_percentage, color: "bg-emerald-400") %></div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<section>
|
||||
<h2 class="mb-3 text-lg font-semibold">Cosa devo fare oggi</h2>
|
||||
<div class="grid gap-4 lg:grid-cols-3">
|
||||
<div>
|
||||
<h3 class="mb-2 text-sm font-semibold uppercase tracking-wide text-rose-700">In ritardo (<%= @overdue_tasks.size %>)</h3>
|
||||
<%= render "shared/task_list", tasks: @overdue_tasks %>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-2 text-sm font-semibold uppercase tracking-wide text-amber-700">Da fare oggi (<%= @today_tasks.size %>)</h3>
|
||||
<%= render "shared/task_list", tasks: @today_tasks %>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-2 text-sm font-semibold uppercase tracking-wide text-zinc-600 dark:text-zinc-400">Prossimi (<%= @upcoming_tasks.size %>)</h3>
|
||||
<%= render "shared/task_list", tasks: @upcoming_tasks %>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<% [
|
||||
["Prospect totali", @metrics.prospect_count],
|
||||
["Contattati", @stage_counts["contacted"]],
|
||||
["Risposte", @stage_counts["replied"]],
|
||||
["Interessati", @stage_counts["interested"]],
|
||||
["Demo/Trial", @stage_counts["demo_trial"]],
|
||||
["Primo utilizzo", @stage_counts["first_use"]],
|
||||
["Proposte", @stage_counts["proposal"]],
|
||||
["Clienti acquisiti", @stage_counts["won"]],
|
||||
["Persi", @stage_counts["lost"]],
|
||||
["Valore WON", format_money(@metrics.won_value)],
|
||||
["Pipeline aperta", format_money(@metrics.open_pipeline_value)],
|
||||
["Task scaduti", @metrics.overdue_tasks_count]
|
||||
].each do |label, value| %>
|
||||
<div 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-4">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-slate-500"><%= label %></div>
|
||||
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= value %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-4 lg:grid-cols-2">
|
||||
<div 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">
|
||||
<h2 class="text-lg font-semibold">Funnel commerciale</h2>
|
||||
<div class="mt-4 space-y-2">
|
||||
<% @funnel.each_with_index do |step, idx| %>
|
||||
<% if step[:rate] %>
|
||||
<div class="text-center text-xs font-medium text-slate-500">↓ <%= step[:rate] %>%</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between rounded-lg bg-zinc-50 px-4 py-3 dark:bg-zinc-800">
|
||||
<span class="font-medium text-zinc-800 dark:text-zinc-100"><%= step[:label].upcase %></span>
|
||||
<span class="text-xl font-semibold tabular-nums"><%= step[:count] %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div 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">
|
||||
<h2 class="text-lg font-semibold">KPI processo</h2>
|
||||
<dl class="mt-4 space-y-3 text-sm">
|
||||
<div class="flex justify-between"><dt class="text-slate-500">Giorni medi contatto → WON</dt><dd class="font-semibold"><%= @metrics.avg_days_to_won || "—" %></dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500">Giorni medi dall'ultima attività</dt><dd class="font-semibold"><%= @metrics.avg_days_since_last_activity || "—" %></dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500">Prospect senza attività > 7gg</dt><dd class="font-semibold"><%= @metrics.prospects_without_activity_over_7_days %></dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500">Opp. senza next action</dt><dd class="font-semibold"><%= @metrics.opportunities_without_next_action %></dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500">Demo/Trial → WON</dt><dd class="font-semibold"><%= @metrics.demo_to_won_conversion %>%</dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500">First Use → WON</dt><dd class="font-semibold"><%= @metrics.first_use_to_won_conversion %>%</dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500">ARPA (WON)</dt><dd class="font-semibold"><%= format_money(@metrics.arpa) %></dd></div>
|
||||
<div class="flex justify-between"><dt class="text-slate-500">Pipeline value</dt><dd class="font-semibold"><%= format_money(@metrics.open_pipeline_value) %></dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-xl border border-amber-200 bg-amber-50 p-5 text-zinc-900 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-50">
|
||||
<h2 class="text-lg font-semibold text-amber-900 dark:text-amber-100">Cosa sbloccare adesso</h2>
|
||||
<p class="mt-1 max-w-3xl text-sm text-amber-900/80 dark:text-amber-100/80">
|
||||
Non è un errore. Sono i buchi che bloccano la vendita: un contatto da chiamare, un’email da mandare, un follow-up mancante.
|
||||
Se una lista è vuota, su quel punto sei a posto.
|
||||
</p>
|
||||
|
||||
<% if @attention[:to_send_count].to_i.positive? %>
|
||||
<div class="mt-4 rounded-lg border border-amber-300 bg-white/70 p-4 dark:border-amber-800 dark:bg-amber-900/40">
|
||||
<h3 class="text-sm font-semibold text-amber-900 dark:text-amber-100">Campagna lancio · email da inviare (<%= @attention[:to_send_count] %>)</h3>
|
||||
<p class="mt-1 text-sm text-zinc-700 dark:text-amber-50/90">
|
||||
Le società sono censite e in stato <strong>Da contattare</strong>. Il passo successivo è mandare le email del test A/B
|
||||
(10–15 al giorno, A e B insieme). Non serve ancora mettere un prezzo o creare un follow-up: quelli arrivano dopo la risposta.
|
||||
</p>
|
||||
<p class="mt-2 text-sm">
|
||||
<%= link_to "Apri la lista da inviare →", organizations_path, class: "font-medium text-zinc-900 underline dark:text-amber-50" %>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-200">Interessati senza follow-up</h3>
|
||||
<p class="mt-0.5 text-xs text-amber-900/70 dark:text-amber-100/70">Hanno detto sì in linea: manca la prossima chiamata/email in agenda.</p>
|
||||
<%= render "dashboard/attention_list", items: @attention[:interested_without_followup], empty: "Nessuno: nessun interessato scoperto." %>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-200">Opportunità ferme da oltre 7 giorni</h3>
|
||||
<p class="mt-0.5 text-xs text-amber-900/70 dark:text-amber-100/70">Lo stage non si è mosso: va rilanciato o chiuso come perso.</p>
|
||||
<%= render "dashboard/attention_list", items: @attention[:stalled_opportunities], empty: "Nessuna: nessuna trattativa ferma." do |opp| %>
|
||||
<%= link_to "#{opp.organization.name} (#{opp.days_in_current_stage}gg)", opp.organization, class: "hover:underline" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-200">Task scaduti</h3>
|
||||
<p class="mt-0.5 text-xs text-amber-900/70 dark:text-amber-100/70">Promemoria già passati: completarli o ripianificarli.</p>
|
||||
<%= render "dashboard/attention_list", items: @attention[:overdue_tasks], empty: "Nessuno: nessun task in ritardo." do |task| %>
|
||||
<%= link_to "#{task.organization.name}: #{task.title}", task.organization, class: "hover:underline" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-200">Senza contatto</h3>
|
||||
<p class="mt-0.5 text-xs text-amber-900/70 dark:text-amber-100/70">Manca una persona a cui scrivere o telefonare.</p>
|
||||
<%= render "dashboard/attention_list", items: @attention[:organizations_without_contacts], empty: "Nessuna: tutte hanno un contatto." do |org| %>
|
||||
<%= link_to org.name, org, class: "hover:underline" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-200">Senza valore (già in trattativa)</h3>
|
||||
<p class="mt-0.5 text-xs text-amber-900/70 dark:text-amber-100/70">Dopo il primo contatto serve un importo stimato (Light/Full…) per la pipeline.</p>
|
||||
<%= render "dashboard/attention_list", items: @attention[:opportunities_without_value], empty: "Nessuna in trattativa senza valore." %>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-200">Senza prossima azione (già in trattativa)</h3>
|
||||
<p class="mt-0.5 text-xs text-amber-900/70 dark:text-amber-100/70">Dopo l’invio o la risposta manca un task in agenda: la trattativa si ferma.</p>
|
||||
<%= render "dashboard/attention_list", items: @attention[:opportunities_without_next_action], empty: "Nessuna in trattativa senza next action." %>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
Reference in New Issue
Block a user