Rende il CRM comodo da usare anche da cellulare.
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:
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,50 @@
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-semibold">Contatti</h1>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "Export CSV", contacts_path(format: :csv), class: btn_secondary %>
|
||||
<%= link_to "Nuovo", new_contact_path, class: btn_primary %>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h1 class="text-xl font-semibold md:text-2xl">Contatti</h1>
|
||||
<div class="flex w-full flex-wrap gap-2 sm:w-auto">
|
||||
<%= 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" %>
|
||||
</div>
|
||||
</div>
|
||||
<%= 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 %>
|
||||
<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="<%= table_class %>">
|
||||
<thead class="bg-zinc-50 text-left text-xs uppercase text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr><th class="px-4 py-3">Nome</th><th class="px-4 py-3">Organizzazione</th><th class="px-4 py-3">Email</th><th class="px-4 py-3">Telefono</th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @contacts.each do |c| %>
|
||||
<tr>
|
||||
<td class="px-4 py-3 font-medium"><%= link_to c.full_name, c.organization, class: "hover:underline" %><% if c.primary_contact? %> ★<% end %></td>
|
||||
<td class="px-4 py-3"><%= c.organization.name %></td>
|
||||
<td class="px-4 py-3"><%= c.email %></td>
|
||||
<td class="px-4 py-3"><%= c.phone.presence || c.mobile %></td>
|
||||
</tr>
|
||||
|
||||
<div class="space-y-2 md:hidden">
|
||||
<% @contacts.each do |c| %>
|
||||
<%= link_to c.organization, class: "#{card_class} block p-4 active:bg-zinc-50 dark:active:bg-zinc-800" do %>
|
||||
<div class="font-semibold"><%= c.full_name %><% if c.primary_contact? %> ★<% end %></div>
|
||||
<div class="mt-0.5 text-sm text-zinc-500"><%= c.organization.name %></div>
|
||||
<div class="mt-1 text-sm break-anywhere text-zinc-600 dark:text-zinc-300"><%= c.email %></div>
|
||||
<% if c.phone.present? || c.mobile.present? %>
|
||||
<div class="mt-0.5 text-sm text-zinc-500"><%= c.phone.presence || c.mobile %></div>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @contacts.blank? %>
|
||||
<p class="<%= card_class %> p-6 text-sm text-zinc-500">Nessun contatto.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="hidden <%= card_class %> md:block">
|
||||
<div class="<%= table_wrap_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="bg-zinc-50 text-left text-xs uppercase text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr><th class="px-4 py-3">Nome</th><th class="px-4 py-3">Organizzazione</th><th class="px-4 py-3">Email</th><th class="px-4 py-3">Telefono</th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @contacts.each do |c| %>
|
||||
<tr>
|
||||
<td class="px-4 py-3 font-medium"><%= link_to c.full_name, c.organization, class: "hover:underline" %><% if c.primary_contact? %> ★<% end %></td>
|
||||
<td class="px-4 py-3"><%= c.organization.name %></td>
|
||||
<td class="px-4 py-3"><%= c.email %></td>
|
||||
<td class="px-4 py-3"><%= c.phone.presence || c.mobile %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<%== pagy_nav(@pagy) if @pagy.pages > 1 %>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
<div class="space-y-4">
|
||||
<h1 class="text-2xl font-semibold"><%= @contact.full_name %></h1>
|
||||
<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 text-sm">
|
||||
<p><strong>Organizzazione:</strong> <%= link_to @contact.organization.name, @contact.organization %></p>
|
||||
<p><strong>Ruolo:</strong> <%= @contact.role %></p>
|
||||
<p><strong>Email:</strong> <%= @contact.email %></p>
|
||||
<p><strong>Telefono:</strong> <%= @contact.phone %></p>
|
||||
<p><strong>Cellulare:</strong> <%= @contact.mobile %></p>
|
||||
<%= link_to "Modifica", edit_contact_path(@contact), class: "mt-4 inline-block text-emerald-700 dark:text-emerald-400" %>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold md:text-2xl"><%= @contact.full_name %></h1>
|
||||
<p class="text-sm text-zinc-500"><%= link_to @contact.organization.name, @contact.organization, class: "hover:underline" %></p>
|
||||
</div>
|
||||
<%= link_to "Modifica", edit_contact_path(@contact), class: "#{btn_secondary} w-full sm:w-auto" %>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-zinc-200 bg-white p-5 text-sm text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<dl class="space-y-4">
|
||||
<div>
|
||||
<dt class="text-xs font-medium uppercase tracking-wide text-zinc-500">Ruolo</dt>
|
||||
<dd class="mt-1"><%= @contact.role.presence || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-medium uppercase tracking-wide text-zinc-500">Email</dt>
|
||||
<dd class="mt-1 break-anywhere"><%= mailto_link(@contact.email, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-medium uppercase tracking-wide text-zinc-500">Telefono</dt>
|
||||
<dd class="mt-1"><%= tel_link(@contact.phone, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-medium uppercase tracking-wide text-zinc-500">Cellulare</dt>
|
||||
<dd class="mt-1"><%= tel_link(@contact.mobile, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<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>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">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 %>
|
||||
<%= link_to "Apri pagina Oggi →", today_path, class: "#{btn_primary} w-full sm:w-auto" %>
|
||||
</div>
|
||||
|
||||
<% if @goal %>
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<section class="grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-4">
|
||||
<% [
|
||||
["Prospect totali", @metrics.prospect_count],
|
||||
["Contattati", @stage_counts["contacted"]],
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
<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">
|
||||
<dl class="mt-4 space-y-3 text-sm break-anywhere">
|
||||
<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>
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
})();
|
||||
</script>
|
||||
<title><%= page_title %><% if current_project %> · <%= current_project.name %><% end %> · <%= app_name %></title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="application-name" content="<%= app_name %>">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
@@ -60,63 +62,58 @@
|
||||
</aside>
|
||||
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<header class="sticky top-0 z-20 border-b border-zinc-200 bg-white/90 backdrop-blur dark:border-zinc-800 dark:bg-zinc-950/90">
|
||||
<div class="flex flex-wrap items-center gap-3 px-4 py-3 md:px-6">
|
||||
<div class="md:hidden">
|
||||
<%= link_to root_path, class: "flex items-center gap-2 font-semibold tracking-tight" do %>
|
||||
<header class="sticky top-0 z-20 border-b border-zinc-200 bg-white/90 pt-[env(safe-area-inset-top)] backdrop-blur dark:border-zinc-800 dark:bg-zinc-950/90">
|
||||
<div class="flex items-center gap-2 px-3 py-2 md:gap-3 md:px-6 md:py-3">
|
||||
<div class="min-w-0 md:hidden">
|
||||
<%= 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 %>
|
||||
<span><%= current_project.name %></span>
|
||||
<span class="truncate"><%= current_project.name %></span>
|
||||
<% end %>
|
||||
<%= link_to "Progetti", root_path, class: "text-xs text-zinc-500" %>
|
||||
</div>
|
||||
<%= 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 %>
|
||||
<div class="relative" data-controller="dropdown">
|
||||
<button type="button" data-action="dropdown#toggle" class="<%= btn_primary %>">+ Nuovo</button>
|
||||
<div class="relative shrink-0" data-controller="dropdown">
|
||||
<button type="button" data-action="dropdown#toggle" class="<%= btn_primary %> px-3">
|
||||
<span class="md:hidden">+</span>
|
||||
<span class="hidden md:inline">+ Nuovo</span>
|
||||
</button>
|
||||
<div data-dropdown-target="menu" class="absolute right-0 z-30 mt-2 hidden w-48 rounded-lg border border-zinc-200 bg-white py-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<%= link_to "Organizzazione", new_organization_path, class: "block px-4 py-2 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Contatto", new_contact_path, class: "block px-4 py-2 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Opportunità", new_opportunity_path, class: "block px-4 py-2 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Task", new_task_path, class: "block px-4 py-2 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Invio email", new_mailing_path, class: "block px-4 py-2 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Organizzazione", new_organization_path, class: "block px-4 py-2.5 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Contatto", new_contact_path, class: "block px-4 py-2.5 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Opportunità", new_opportunity_path, class: "block px-4 py-2.5 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Task", new_task_path, class: "block px-4 py-2.5 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Invio email", new_mailing_path, class: "block px-4 py-2.5 text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800" %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render "shared/theme_toggle" %>
|
||||
<%= render "shared/user_menu" %>
|
||||
</div>
|
||||
<nav class="flex gap-1 overflow-x-auto border-t border-zinc-100 px-2 py-2 dark:border-zinc-800 md:hidden">
|
||||
<%= link_to "Home", project_root_path, class: "whitespace-nowrap rounded px-2 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300" %>
|
||||
<%= link_to "Oggi", today_path, class: "whitespace-nowrap rounded px-2 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300" %>
|
||||
<%= link_to "Org", organizations_path, class: "whitespace-nowrap rounded px-2 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300" %>
|
||||
<%= link_to "Pipeline", pipeline_path, class: "whitespace-nowrap rounded px-2 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300" %>
|
||||
<%= link_to "Task", tasks_path, class: "whitespace-nowrap rounded px-2 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300" %>
|
||||
<%= link_to "Email", dashboard_mailings_path, class: "whitespace-nowrap rounded px-2 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300" %>
|
||||
<% if current_user.admin? %>
|
||||
<%= link_to "Utenti", users_path, class: "whitespace-nowrap rounded px-2 py-1 text-xs font-medium text-zinc-700 dark:text-zinc-300" %>
|
||||
<% end %>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="min-w-0 flex-1 overflow-x-hidden p-4 md:p-6">
|
||||
<main class="min-w-0 flex-1 overflow-x-hidden p-4 pb-24 md:p-6">
|
||||
<div id="flash"><%= render "shared/flash" %></div>
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:hidden" data-controller="sheet">
|
||||
<%= render "shared/mobile_bottom_nav" %>
|
||||
<%= render "shared/mobile_more_sheet" %>
|
||||
</div>
|
||||
<% elsif logged_in? %>
|
||||
<div data-controller="modal" data-modal-open-value="<%= params[:new_project].present? %>">
|
||||
<header class="border-b border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-950">
|
||||
<div class="mx-auto flex max-w-5xl items-center justify-between gap-3 px-4 py-4">
|
||||
<div>
|
||||
<header class="border-b border-zinc-200 bg-white pt-[env(safe-area-inset-top)] dark:border-zinc-800 dark:bg-zinc-950">
|
||||
<div class="mx-auto flex max-w-5xl items-center justify-between gap-3 px-4 py-3 md:py-4">
|
||||
<div class="min-w-0">
|
||||
<%= link_to root_path, class: "text-zinc-900 dark:text-zinc-100" do %>
|
||||
<%= render "shared/logo", subtitle: (@page_title || "Seleziona un'istanza progetto") %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<% if current_user.admin? %>
|
||||
<button type="button" data-action="modal#show" class="<%= btn_primary %>" aria-label="Nuovo progetto" title="Nuovo progetto">+</button>
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="absolute right-4 top-4 z-10">
|
||||
<div class="absolute right-4 top-[max(1rem,env(safe-area-inset-top))] z-10">
|
||||
<%= render "shared/theme_toggle" %>
|
||||
</div>
|
||||
<main class="min-h-screen">
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
<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">Account email / SMTP</h1>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Account email / SMTP</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
<%= link_to "Impostazioni", admin_path, class: "hover:underline" %>
|
||||
· da questi indirizzi partono le campagne. Le password SMTP sono cifrate.
|
||||
</p>
|
||||
</div>
|
||||
<%= 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" %>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden <%= card_class %>">
|
||||
<div class="<%= card_class %>">
|
||||
<% if @mail_identities.empty? %>
|
||||
<p class="p-6 text-sm text-zinc-500">Nessun account. Crea il mittente con host, porta e credenziali SMTP.</p>
|
||||
<% else %>
|
||||
<table class="<%= table_class %>">
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800 md:hidden">
|
||||
<% @mail_identities.each do |identity| %>
|
||||
<div class="p-4 <%= "opacity-60" unless identity.active? %>">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-semibold"><%= identity.name %></div>
|
||||
<div class="mt-0.5 text-sm text-zinc-500"><%= identity.from_name %></div>
|
||||
<div class="mt-0.5 break-anywhere text-sm text-zinc-600 dark:text-zinc-300"><%= identity.from_email %></div>
|
||||
</div>
|
||||
<%= link_to "Modifica", edit_mail_identity_path(identity), class: "text-sm text-zinc-700 hover:underline dark:text-zinc-300" %>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-zinc-600 dark:text-zinc-300"><%= identity.smtp_host %>:<%= identity.smtp_port %> · <%= Catalog.label_for(Catalog::MAIL_ENCRYPTIONS, identity.encryption) %></div>
|
||||
<div class="mt-1 text-sm text-zinc-500"><%= identity.active? ? "Attivo" : "Disattivo" %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="<%= table_wrap_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="border-b border-zinc-100 bg-zinc-50 text-left text-xs font-medium uppercase tracking-wide text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Nome</th>
|
||||
@@ -44,6 +62,8 @@
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
<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">Template email</h1>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Template email</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
<%= link_to "Invii", dashboard_mailings_path, class: "hover:underline" %>
|
||||
· HTML riutilizzabile con variabili della scheda cliente.
|
||||
</p>
|
||||
</div>
|
||||
<%= 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" %>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden <%= card_class %>">
|
||||
<div class="<%= card_class %>">
|
||||
<% if @mail_templates.empty? %>
|
||||
<p class="p-6 text-sm text-zinc-500">Nessun template. Creane uno e usalo come base per gli invii.</p>
|
||||
<% else %>
|
||||
<table class="<%= table_class %>">
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800 md:hidden">
|
||||
<% @mail_templates.each do |template| %>
|
||||
<div class="p-4">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-semibold"><%= template.name %></div>
|
||||
<div class="mt-0.5 break-anywhere text-sm text-zinc-600 dark:text-zinc-300"><%= template.subject %></div>
|
||||
<div class="mt-1 text-xs text-zinc-500"><%= template.project_id? ? current_project.name : "Tutti i progetti" %></div>
|
||||
</div>
|
||||
<%= link_to "Modifica", edit_mail_template_path(template), class: "text-sm text-zinc-700 hover:underline dark:text-zinc-300" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="<%= table_wrap_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="border-b border-zinc-100 bg-zinc-50 text-left text-xs font-medium uppercase tracking-wide text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Nome</th>
|
||||
@@ -36,6 +52,8 @@
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
<%= tag.div class: "space-y-6", data: (refresh ? { controller: "auto-reload", auto_reload_interval_value: 10_000 } : {}) do %>
|
||||
<div class="flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Invii email</h1>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Invii email</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">Stato delle campagne, prove e invii di massa scaglionati.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= link_to "Template", mail_templates_path, class: btn_secondary %>
|
||||
<div class="flex w-full flex-wrap gap-2 sm:w-auto">
|
||||
<%= 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" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div class="grid grid-cols-2 gap-3 xl:grid-cols-4">
|
||||
<div class="<%= card_class %> p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-zinc-500">In corso</div>
|
||||
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @active_mailings.size %></div>
|
||||
@@ -56,11 +56,33 @@
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<div class="overflow-hidden <%= card_class %>">
|
||||
<div class="<%= card_class %>">
|
||||
<% if @mailings.empty? %>
|
||||
<p class="p-6 text-sm text-zinc-500">Nessun invio. Crea una bozza, fai una prova con dati manuali e poi autorizza l’invio di massa.</p>
|
||||
<% else %>
|
||||
<table class="<%= table_class %>">
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800 md:hidden">
|
||||
<% @mailings.each do |mailing| %>
|
||||
<%= link_to mailing, 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"><%= mailing.name %></div>
|
||||
<div class="mt-0.5 text-xs text-zinc-500 break-anywhere"><%= mailing.mail_identity.from_email %></div>
|
||||
</div>
|
||||
<%= mailing_status_badge(mailing.status) %>
|
||||
</div>
|
||||
<div class="mt-3"><%= progress_bar(mailing.progress_percentage) %></div>
|
||||
<div class="mt-2 text-xs text-zinc-500">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="<%= table_wrap_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="border-b border-zinc-100 bg-zinc-50 text-left text-xs font-medium uppercase tracking-wide text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Invio</th>
|
||||
@@ -107,7 +129,9 @@
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%= tag.div class: "space-y-6", data: (@mailing.sending? ? { controller: "auto-reload", auto_reload_interval_value: 10_000 } : {}) do %>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight"><%= @mailing.name %></h1>
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl"><%= @mailing.name %></h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
<%= mailing_status_badge(@mailing.status) %>
|
||||
· <%= @mailing.audience_label %>
|
||||
@@ -25,8 +25,8 @@
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= link_to "Cruscotto invii", dashboard_mailings_path, class: btn_secondary %>
|
||||
<div class="flex w-full flex-wrap gap-2 sm:w-auto">
|
||||
<%= 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 %>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-4">
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<div class="<%= card_class %> p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-zinc-500">Da inviare</div>
|
||||
<div class="mt-1 text-2xl font-semibold"><%= @mailing.pending_count %></div>
|
||||
@@ -103,68 +103,113 @@
|
||||
</div>
|
||||
|
||||
<%= form_with url: update_recipients_mailing_path(@mailing), method: :patch do %>
|
||||
<div class="overflow-x-auto" data-controller="check-all">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="border-b border-zinc-100 bg-zinc-50 text-left text-xs font-medium uppercase tracking-wide text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr>
|
||||
<div data-controller="check-all">
|
||||
<% if @mailing.editable? %>
|
||||
<label class="flex min-h-11 items-center gap-2 border-b border-zinc-100 px-4 py-3 text-sm dark:border-zinc-800">
|
||||
<input type="checkbox" data-check-all-target="source" data-action="check-all#toggle" aria-label="Seleziona tutti">
|
||||
Seleziona tutti
|
||||
</label>
|
||||
<% end %>
|
||||
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800 md:hidden">
|
||||
<% @recipients.each do |recipient| %>
|
||||
<div class="flex gap-3 p-4 <%= "opacity-60" unless recipient.email_ok? %>">
|
||||
<% if @mailing.editable? %>
|
||||
<th class="px-4 py-3">
|
||||
<input type="checkbox" data-check-all-target="source" data-action="check-all#toggle" aria-label="Seleziona tutti">
|
||||
</th>
|
||||
<div class="pt-1">
|
||||
<% 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 %>
|
||||
<span class="text-zinc-400">—</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<th class="px-4 py-3">Organizzazione</th>
|
||||
<th class="px-4 py-3">Email</th>
|
||||
<% if @mailing.ab_test? %>
|
||||
<th class="px-4 py-3">Test</th>
|
||||
<% end %>
|
||||
<th class="px-4 py-3">Stato</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @recipients.each do |recipient| %>
|
||||
<tr class="<%= "opacity-60" unless recipient.email_ok? %>">
|
||||
<% if @mailing.editable? %>
|
||||
<td class="px-4 py-3">
|
||||
<% if recipient.email_ok? %>
|
||||
<%= check_box_tag "pending_ids[]", recipient.id, recipient.pending?,
|
||||
id: "recipient_#{recipient.id}",
|
||||
data: { check_all_target: "checkbox" } %>
|
||||
<% else %>
|
||||
<span class="text-zinc-400">—</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
<td class="px-4 py-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<%= link_to recipient.organization.name, recipient.organization, class: "font-medium hover:underline" %>
|
||||
<div class="text-xs text-zinc-500"><%= recipient.contact&.full_name.presence || "Nessun contatto" %></div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<%= mailing_recipient_status_badge(recipient.status) %>
|
||||
<% if @mailing.ab_test? %><%= ab_variant_badge(recipient.ab_variant) %><% end %>
|
||||
</div>
|
||||
<div class="mt-1 text-sm break-anywhere text-zinc-600 dark:text-zinc-300">
|
||||
<%= recipient.contact&.full_name.presence || "Nessun contatto" %>
|
||||
·
|
||||
<% if recipient.email_ok? %>
|
||||
<%= recipient.email %>
|
||||
<% else %>
|
||||
<span class="text-rose-700"><%= recipient.email.presence || "manca email" %></span>
|
||||
<% end %>
|
||||
<% if recipient.skip_reason.present? && !recipient.pending? %>
|
||||
<div class="text-xs text-zinc-500"><%= recipient.skip_reason %></div>
|
||||
<% end %>
|
||||
<% if recipient.error_message.present? %>
|
||||
<div class="text-xs text-rose-700"><%= recipient.error_message %></div>
|
||||
<% end %>
|
||||
</td>
|
||||
<% if @mailing.ab_test? %>
|
||||
<td class="px-4 py-3"><%= ab_variant_badge(recipient.ab_variant) || "—" %></td>
|
||||
</div>
|
||||
<% if recipient.skip_reason.present? && !recipient.pending? %>
|
||||
<div class="mt-1 text-xs text-zinc-500"><%= recipient.skip_reason %></div>
|
||||
<% end %>
|
||||
<td class="px-4 py-3"><%= mailing_recipient_status_badge(recipient.status) %></td>
|
||||
<td class="px-4 py-3 text-right text-sm">
|
||||
<% 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? %>
|
||||
<div class="mt-1 text-xs text-rose-700"><%= recipient.error_message %></div>
|
||||
<% 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 %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:block">
|
||||
<div class="<%= table_wrap_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="border-b border-zinc-100 bg-zinc-50 text-left text-xs font-medium uppercase tracking-wide text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Organizzazione</th>
|
||||
<th class="px-4 py-3">Email</th>
|
||||
<% if @mailing.ab_test? %>
|
||||
<th class="px-4 py-3">Test</th>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<th class="px-4 py-3">Stato</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @recipients.each do |recipient| %>
|
||||
<tr class="<%= "opacity-60" unless recipient.email_ok? %>">
|
||||
<td class="px-4 py-3">
|
||||
<% 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" %>
|
||||
<div class="text-xs text-zinc-500"><%= recipient.contact&.full_name.presence || "Nessun contatto" %></div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<% if recipient.email_ok? %>
|
||||
<%= recipient.email %>
|
||||
<% else %>
|
||||
<span class="text-rose-700"><%= recipient.email.presence || "manca email" %></span>
|
||||
<% end %>
|
||||
<% if recipient.skip_reason.present? && !recipient.pending? %>
|
||||
<div class="text-xs text-zinc-500"><%= recipient.skip_reason %></div>
|
||||
<% end %>
|
||||
<% if recipient.error_message.present? %>
|
||||
<div class="text-xs text-rose-700"><%= recipient.error_message %></div>
|
||||
<% end %>
|
||||
</td>
|
||||
<% if @mailing.ab_test? %>
|
||||
<td class="px-4 py-3"><%= ab_variant_badge(recipient.ab_variant) || "—" %></td>
|
||||
<% end %>
|
||||
<td class="px-4 py-3"><%= mailing_recipient_status_badge(recipient.status) %></td>
|
||||
<td class="px-4 py-3 text-right text-sm">
|
||||
<% 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 %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @mailing.editable? %>
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-semibold">Opportunità</h1>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "Export CSV", opportunities_path(format: :csv), class: btn_secondary %>
|
||||
<%= link_to "Nuova", new_opportunity_path, class: btn_primary %>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h1 class="text-xl font-semibold md:text-2xl">Opportunità</h1>
|
||||
<div class="flex w-full flex-wrap gap-2 sm:w-auto">
|
||||
<%= 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" %>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<div class="space-y-2 md:hidden">
|
||||
<% @opportunities.each do |opp| %>
|
||||
<%= link_to opp.organization, 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"><%= opp.organization.name %></div>
|
||||
<div class="mt-0.5 text-sm text-zinc-500"><%= opp.name %></div>
|
||||
</div>
|
||||
<%= stage_badge(opp.pipeline_stage) %>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-zinc-600 dark:text-zinc-300"><%= format_money(opp.estimated_value) %> · <%= opp.assigned_user&.full_name || "—" %></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="hidden <%= card_class %> md:block">
|
||||
<div class="<%= table_wrap_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="bg-zinc-50 text-left text-xs uppercase text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr><th class="px-4 py-3">Organizzazione</th><th class="px-4 py-3">Nome</th><th class="px-4 py-3">Stage</th><th class="px-4 py-3">Valore</th><th class="px-4 py-3">Owner</th></tr>
|
||||
@@ -22,7 +37,8 @@
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<%== pagy_nav(@pagy) if @pagy.pages > 1 %>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="space-y-6">
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Report</h1>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Report</h1>
|
||||
|
||||
<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">
|
||||
<section class="rounded-xl border border-zinc-200 bg-white p-5 text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<h2 class="text-lg font-semibold">Funnel</h2>
|
||||
<div class="mt-4 overflow-x-auto">
|
||||
<table class="min-w-full text-sm text-zinc-900 dark:text-zinc-100">
|
||||
@@ -28,97 +28,103 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<section class="rounded-xl border border-zinc-200 bg-white p-5 text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<h2 class="text-lg font-semibold">Lead source</h2>
|
||||
<table class="mt-3 min-w-full text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<thead>
|
||||
<tr class="text-left text-xs uppercase tracking-wide text-zinc-500">
|
||||
<th class="py-2">Fonte</th>
|
||||
<th>Lead</th>
|
||||
<th>Opp.</th>
|
||||
<th>Clienti</th>
|
||||
<th>Conv.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @lead_sources.each do |row| %>
|
||||
<tr>
|
||||
<td class="py-2"><%= row[:label] %></td>
|
||||
<td><%= row[:leads] %></td>
|
||||
<td><%= row[:opportunities] %></td>
|
||||
<td><%= row[:customers] %></td>
|
||||
<td><%= row[:conversion_rate] %>%</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<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">
|
||||
<h2 class="text-lg font-semibold">Won / Lost per mese</h2>
|
||||
<table class="mt-3 min-w-full text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<div class="mt-3 <%= table_wrap_class %>">
|
||||
<table class="min-w-full text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<thead>
|
||||
<tr class="text-left text-xs uppercase tracking-wide text-zinc-500">
|
||||
<th class="py-2">Mese</th>
|
||||
<th>Won</th>
|
||||
<th>Lost</th>
|
||||
<th class="py-2">Fonte</th>
|
||||
<th>Lead</th>
|
||||
<th>Opp.</th>
|
||||
<th>Clienti</th>
|
||||
<th>Conv.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @won_lost.each do |row| %>
|
||||
<% @lead_sources.each do |row| %>
|
||||
<tr>
|
||||
<td class="py-2"><%= l(row[:month], format: "%B %Y") %></td>
|
||||
<td><%= row[:won] %></td>
|
||||
<td><%= row[:lost] %></td>
|
||||
<td class="py-2"><%= row[:label] %></td>
|
||||
<td><%= row[:leads] %></td>
|
||||
<td><%= row[:opportunities] %></td>
|
||||
<td><%= row[:customers] %></td>
|
||||
<td><%= row[:conversion_rate] %>%</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<section class="rounded-xl border border-zinc-200 bg-white p-5 text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<h2 class="text-lg font-semibold">Won / Lost per mese</h2>
|
||||
<div class="mt-3 <%= table_wrap_class %>">
|
||||
<table class="min-w-full text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<thead>
|
||||
<tr class="text-left text-xs uppercase tracking-wide text-zinc-500">
|
||||
<th class="py-2">Mese</th>
|
||||
<th>Won</th>
|
||||
<th>Lost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @won_lost.each do |row| %>
|
||||
<tr>
|
||||
<td class="py-2"><%= l(row[:month], format: "%B %Y") %></td>
|
||||
<td><%= row[:won] %></td>
|
||||
<td><%= row[:lost] %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<section class="rounded-xl border border-zinc-200 bg-white p-5 text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<h2 class="text-lg font-semibold">Lost reasons</h2>
|
||||
<ul class="mt-3 space-y-2 text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<% @lost_reasons.each do |row| %>
|
||||
<li class="flex justify-between"><span><%= row[:label] %></span><strong><%= row[:count] %></strong></li>
|
||||
<li class="flex justify-between gap-3"><span><%= row[:label] %></span><strong><%= row[:count] %></strong></li>
|
||||
<% end %>
|
||||
<% if @lost_reasons.blank? %><li class="text-zinc-500">Nessun dato</li><% end %>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<section class="rounded-xl border border-zinc-200 bg-white p-5 text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<h2 class="text-lg font-semibold">Sales owner</h2>
|
||||
<table class="mt-3 min-w-full text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<thead>
|
||||
<tr class="text-left text-xs uppercase tracking-wide text-zinc-500">
|
||||
<th class="py-2">Utente</th>
|
||||
<th>Aperte</th>
|
||||
<th>Valore aperte</th>
|
||||
<th>Vinte</th>
|
||||
<th>Valore vinte</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @sales_owners.each do |row| %>
|
||||
<tr>
|
||||
<td class="py-2"><%= row[:user].full_name %></td>
|
||||
<td><%= row[:open_count] %></td>
|
||||
<td><%= format_money(row[:open_value]) %></td>
|
||||
<td><%= row[:won_count] %></td>
|
||||
<td><%= format_money(row[:won_value]) %></td>
|
||||
<div class="mt-3 <%= table_wrap_class %>">
|
||||
<table class="min-w-full text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<thead>
|
||||
<tr class="text-left text-xs uppercase tracking-wide text-zinc-500">
|
||||
<th class="py-2">Utente</th>
|
||||
<th>Aperte</th>
|
||||
<th>Valore aperte</th>
|
||||
<th>Vinte</th>
|
||||
<th>Valore vinte</th>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% @sales_owners.each do |row| %>
|
||||
<tr>
|
||||
<td class="py-2"><%= row[:user].full_name %></td>
|
||||
<td><%= row[:open_count] %></td>
|
||||
<td><%= format_money(row[:open_value]) %></td>
|
||||
<td><%= row[:won_count] %></td>
|
||||
<td><%= format_money(row[:won_value]) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<section class="rounded-xl border border-zinc-200 bg-white p-5 text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100">
|
||||
<h2 class="text-lg font-semibold">Revenue (WON per mese)</h2>
|
||||
<ul class="mt-3 space-y-2 text-sm text-zinc-900 dark:text-zinc-100">
|
||||
<% @revenue.each do |row| %>
|
||||
<li class="flex justify-between"><span><%= l(row[:month], format: "%B %Y") %></span><strong><%= format_money(row[:value]) %></strong></li>
|
||||
<li class="flex justify-between gap-3"><span><%= l(row[:month], format: "%B %Y") %></span><strong><%= format_money(row[:value]) %></strong></li>
|
||||
<% end %>
|
||||
<% if @revenue.blank? %><li class="text-zinc-500">Nessun dato</li><% end %>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<nav id="mobile-bottom-nav" class="fixed inset-x-0 bottom-0 z-30 border-t border-zinc-200 bg-white/95 pb-[env(safe-area-inset-bottom)] backdrop-blur dark:border-zinc-800 dark:bg-zinc-950/95 md:hidden">
|
||||
<div class="flex items-stretch">
|
||||
<%= link_to today_path, class: mobile_tab_class(today_path, controllers: %w[today]) do %>
|
||||
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<rect x="3.5" y="5" width="17" height="15" rx="2" />
|
||||
<path stroke-linecap="round" d="M8 3.5v3M16 3.5v3M3.5 9.5h17" />
|
||||
</svg>
|
||||
<span>Oggi</span>
|
||||
<% end %>
|
||||
<%= link_to organizations_path, class: mobile_tab_class(organizations_path, controllers: %w[organizations contacts]) do %>
|
||||
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 20V8.5L12 4l8 4.5V20" />
|
||||
<path stroke-linecap="round" d="M9 20v-6h6v6" />
|
||||
</svg>
|
||||
<span>Org</span>
|
||||
<% end %>
|
||||
<%= link_to pipeline_path, class: mobile_tab_class(pipeline_path, controllers: %w[pipeline opportunities]) do %>
|
||||
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<path stroke-linecap="round" d="M5 5v14M12 5v14M19 5v14" />
|
||||
</svg>
|
||||
<span>Pipeline</span>
|
||||
<% end %>
|
||||
<%= link_to dashboard_mailings_path, class: mobile_tab_class(dashboard_mailings_path, controllers: %w[mailings mail_templates mail_identities]) do %>
|
||||
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<rect x="3.5" y="5.5" width="17" height="13" rx="2" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4 7 8 6 8-6" />
|
||||
</svg>
|
||||
<span>Email</span>
|
||||
<% end %>
|
||||
<button type="button" data-action="sheet#toggle" class="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 text-zinc-500 dark:text-zinc-400">
|
||||
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.4" fill="currentColor" />
|
||||
<circle cx="12" cy="12" r="1.4" fill="currentColor" />
|
||||
<circle cx="19" cy="12" r="1.4" fill="currentColor" />
|
||||
</svg>
|
||||
<span>Altro</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -0,0 +1,32 @@
|
||||
<div data-sheet-target="panel" class="fixed inset-0 z-40 hidden md:hidden" role="dialog" aria-modal="true" aria-labelledby="mobile-more-title">
|
||||
<button type="button" class="absolute inset-0 bg-zinc-950/40 dark:bg-black/60" data-action="sheet#hide" aria-label="Chiudi"></button>
|
||||
<div class="absolute inset-x-0 bottom-0 max-h-[85vh] overflow-y-auto rounded-t-2xl border border-zinc-200 bg-white pb-[calc(env(safe-area-inset-bottom)+1rem)] shadow-xl dark:border-zinc-800 dark:bg-zinc-950">
|
||||
<div class="sticky top-0 flex items-center justify-between border-b border-zinc-100 bg-white px-4 py-3 dark:border-zinc-800 dark:bg-zinc-950">
|
||||
<h2 id="mobile-more-title" class="text-base font-semibold">Menu</h2>
|
||||
<button type="button" data-action="sheet#hide" class="<%= btn_secondary %> px-3" aria-label="Chiudi">Chiudi</button>
|
||||
</div>
|
||||
<nav class="space-y-1 p-3">
|
||||
<%= nav_link "Dashboard", project_root_path %>
|
||||
<%= nav_link "Contatti", contacts_path %>
|
||||
<%= nav_link "Task", tasks_path %>
|
||||
<%= nav_link "Report", reports_path %>
|
||||
<%= nav_link "Impostazioni", settings_path %>
|
||||
<%= nav_link "Template email", mail_templates_path %>
|
||||
<% if current_user.admin? %>
|
||||
<%= nav_link "Account SMTP", mail_identities_path %>
|
||||
<%= nav_link "Utenti", users_path %>
|
||||
<% end %>
|
||||
</nav>
|
||||
<div class="border-t border-zinc-100 p-3 dark:border-zinc-800">
|
||||
<div class="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400">Cambia progetto</div>
|
||||
<div class="space-y-1">
|
||||
<% available_projects.each do |project| %>
|
||||
<%= link_to project.name,
|
||||
project_root_path(project_code: project.code),
|
||||
class: "block truncate rounded-lg px-3 py-2.5 text-sm #{project.id == current_project.id ? 'bg-zinc-900 text-white dark:bg-zinc-100 dark:text-zinc-900' : 'text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800'}" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= link_to "Tutti i progetti", root_path, class: "mt-2 block rounded-lg px-3 py-2.5 text-sm text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
<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">
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
<% tasks.each do |task| %>
|
||||
<div class="flex flex-wrap items-center gap-3 px-4 py-3 <%= 'bg-rose-50' if task.overdue? %> <%= 'bg-amber-50' if task.due_today? %>">
|
||||
<div class="flex flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center <%= 'bg-rose-50' if task.overdue? %> <%= 'bg-amber-50' if task.due_today? %>">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex w-full flex-wrap items-center gap-2 sm:w-auto">
|
||||
<%= 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" %>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<button type="button"
|
||||
data-controller="theme"
|
||||
data-action="theme#toggle"
|
||||
class="inline-flex size-9 items-center justify-center rounded-lg border border-zinc-200 text-zinc-600 hover:bg-zinc-100 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
class="inline-flex size-11 items-center justify-center rounded-lg border border-zinc-200 text-zinc-600 hover:bg-zinc-100 md:size-9 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
aria-label="Passa al tema scuro o chiaro"
|
||||
title="Tema">
|
||||
<svg class="size-4 dark:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<div class="relative" data-controller="dropdown">
|
||||
<button type="button" data-action="dropdown#toggle" class="flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800">
|
||||
<span class="max-w-[12rem] truncate font-medium text-zinc-900 dark:text-zinc-100"><%= current_user.full_name %></span>
|
||||
<span class="text-xs text-zinc-400"><%= current_user.admin? ? "Admin" : "Utente" %></span>
|
||||
<button type="button" data-action="dropdown#toggle" class="flex size-11 items-center justify-center rounded-lg text-sm text-zinc-600 hover:bg-zinc-100 md:size-auto md:gap-2 md:px-2 md:py-1.5 dark:text-zinc-300 dark:hover:bg-zinc-800">
|
||||
<span class="flex size-9 items-center justify-center rounded-full bg-zinc-900 text-xs font-semibold text-white md:hidden dark:bg-zinc-100 dark:text-zinc-900"><%= user_initials(current_user) %></span>
|
||||
<span class="hidden max-w-[12rem] truncate font-medium text-zinc-900 md:inline dark:text-zinc-100"><%= current_user.full_name %></span>
|
||||
<span class="hidden text-xs text-zinc-400 md:inline"><%= current_user.admin? ? "Admin" : "Utente" %></span>
|
||||
</button>
|
||||
<div data-dropdown-target="menu" class="absolute right-0 z-30 mt-1 hidden w-48 rounded-lg border border-zinc-200 bg-white py-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<% if current_user.admin? %>
|
||||
<%= link_to "Impostazioni", admin_path, class: "block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Utenti", users_path, class: "block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Impostazioni", admin_path, class: "block px-4 py-2.5 text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Utenti", users_path, class: "block px-4 py-2.5 text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
<% end %>
|
||||
<%= link_to "Password", edit_password_path, class: "block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
<%= button_to "Esci", logout_path, method: :delete, class: "block w-full px-4 py-2 text-left text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
<%= link_to "Password", edit_password_path, class: "block px-4 py-2.5 text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
<%= button_to "Esci", logout_path, method: :delete, class: "block w-full px-4 py-2.5 text-left text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-semibold">Task</h1>
|
||||
<%= link_to "Nuovo task", new_task_path, class: btn_primary %>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h1 class="text-xl font-semibold md:text-2xl">Task</h1>
|
||||
<%= link_to "Nuovo task", new_task_path, class: "#{btn_primary} w-full sm:w-auto" %>
|
||||
</div>
|
||||
<section>
|
||||
<h2 class="mb-2 text-sm font-semibold uppercase text-rose-700">In ritardo</h2>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Oggi</h1>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Oggi</h1>
|
||||
<p class="text-sm text-slate-500"><%= l(Time.zone.today, format: :long) %> · pagina operativa del mattino</p>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<%= link_to org.name, org, class: "font-medium hover:underline" %>
|
||||
<div class="text-sm text-slate-500"><%= org.city %> · <%= org.lead_source_label %> · <%= org.assigned_user&.full_name || "Non assegnato" %></div>
|
||||
</div>
|
||||
<%= 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" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @new_prospects.blank? %><p class="text-sm text-slate-500">Nessun nuovo prospect.</p><% end %>
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
<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">Utenti</h1>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Utenti</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
<%= link_to "Impostazioni", admin_path, class: "hover:underline" %>
|
||||
· account globali. Gli admin vedono tutti i progetti; gli utenti solo quelli abilitati.
|
||||
</p>
|
||||
</div>
|
||||
<%= 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" %>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden <%= card_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<div class="<%= card_class %>">
|
||||
<div class="divide-y divide-zinc-100 dark:divide-zinc-800 md:hidden">
|
||||
<% @users.each do |user| %>
|
||||
<div class="p-4 <%= "opacity-60" unless user.active? %>">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="font-semibold"><%= user.full_name %><% if user == current_user %><span class="ml-1 text-xs font-normal text-zinc-400">(tu)</span><% end %></div>
|
||||
<div class="mt-0.5 break-anywhere text-sm text-zinc-500"><%= user.email %></div>
|
||||
</div>
|
||||
<%= link_to "Modifica", edit_user_path(user), class: "text-sm text-zinc-700 hover:underline dark:text-zinc-300" %>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-zinc-600 dark:text-zinc-300"><%= user.admin? ? "Admin" : "Utente" %> · <%= user.active? ? "Attivo" : "Disabilitato" %></div>
|
||||
<div class="mt-1 text-sm text-zinc-500">
|
||||
<% if user.admin? %>
|
||||
Tutti i progetti
|
||||
<% else %>
|
||||
<% names = user.user_projects.select(&:enabled?).filter_map { |up| up.project&.name } %>
|
||||
<%= names.any? ? names.join(", ") : "—" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="<%= table_wrap_class %>">
|
||||
<table class="<%= table_class %>">
|
||||
<thead class="border-b border-zinc-100 bg-zinc-50 text-left text-xs font-medium uppercase tracking-wide text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Nome</th>
|
||||
@@ -53,5 +77,7 @@
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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!(
|
||||
|
||||
Reference in New Issue
Block a user