Files
eminuxCRM/app/helpers/application_helper.rb
T
eminuxandCursor 4cf34b2033
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / scan_ruby (push) Has been cancelled
Non tratta l'EOF SMTP come invio riuscito e toglie quei falsi errori dalla lista.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 12:32:18 +02:00

259 lines
11 KiB
Ruby

module ApplicationHelper
include Pagy::Frontend
def app_name
Rails.application.config.x.app_name
end
def btn_primary
"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 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 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 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)
Array(templates).map { |template| { id: template.id, subject: template.subject, body_html: template.body_html } }
end
def card_class
"rounded-xl border border-zinc-200 bg-white text-zinc-900 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100"
end
def table_class
"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 dark:bg-sky-950 dark:text-sky-200",
"active_customer" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
"inactive_customer" => "bg-slate-100 text-slate-700 dark:bg-zinc-800 dark:text-zinc-300",
"partner" => "bg-violet-100 text-violet-800 dark:bg-violet-950 dark:text-violet-200",
"lost" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200"
}
content_tag :span, Catalog.label_for(Catalog::ORGANIZATION_STATUSES, status),
class: "inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium #{colors[status] || 'bg-slate-100 text-slate-700 dark:bg-zinc-800 dark:text-zinc-300'}"
end
def stage_badge(stage)
colors = {
"to_contact" => "bg-slate-100 text-slate-700 dark:bg-zinc-800 dark:text-zinc-300",
"contacted" => "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-200",
"replied" => "bg-cyan-100 text-cyan-800 dark:bg-cyan-950 dark:text-cyan-200",
"interested" => "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200",
"demo_trial" => "bg-orange-100 text-orange-800 dark:bg-orange-950 dark:text-orange-200",
"first_use" => "bg-lime-100 text-lime-800 dark:bg-lime-950 dark:text-lime-200",
"proposal" => "bg-indigo-100 text-indigo-800 dark:bg-indigo-950 dark:text-indigo-200",
"won" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
"lost" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200"
}
content_tag :span, Catalog.label_for(Catalog::PIPELINE_STAGES, stage),
class: "inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium #{colors[stage] || 'bg-slate-100 text-slate-700 dark:bg-zinc-800 dark:text-zinc-300'}"
end
def priority_badge(priority)
colors = {
"low" => "bg-slate-100 text-slate-600 dark:bg-zinc-800 dark:text-zinc-300",
"normal" => "bg-sky-100 text-sky-700 dark:bg-sky-950 dark:text-sky-200",
"high" => "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200",
"urgent" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200"
}
content_tag :span, Catalog.label_for(Catalog::TASK_PRIORITIES, priority),
class: "inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium #{colors[priority] || 'bg-slate-100 text-slate-700 dark:bg-zinc-800 dark:text-zinc-300'}"
end
def format_money(value)
number_to_currency(value.to_f, unit: "€", separator: ",", delimiter: ".", format: "%n %u")
end
def format_conversion_rate(value)
return "—" if value.nil?
"#{value}%"
end
def format_dt(value)
return "—" if value.blank?
I18n.l(value, format: :short)
end
def format_date(value)
return "—" if value.blank?
I18n.l(value.to_date, format: :default)
end
def progress_bar(percentage, color: "bg-emerald-500")
content_tag :div, class: "h-3 w-full overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-600" do
content_tag :div, "", class: "h-full #{color} transition-all", style: "width: #{percentage.to_i}%"
end
end
def nav_link(label, path, icon: nil)
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"
end
link_to path, class: "flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium #{classes}" do
label
end
end
def options_for_catalog(hash, selected = nil)
options_for_select(hash.map { |k, v| [v, k] }, selected)
end
def mailing_status_badge(status)
colors = {
"draft" => "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200",
"sending" => "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200",
"sent" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
"cancelled" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200"
}
content_tag :span, Catalog.label_for(Catalog::MAILING_STATUSES, status),
class: "inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium #{colors[status] || 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200'}"
end
def mailing_recipient_status_badge(status)
colors = {
"pending" => "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-200",
"skipped" => "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300",
"queued" => "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200",
"sent" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
"failed" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200"
}
content_tag :span, Catalog.label_for(Catalog::MAILING_RECIPIENT_STATUSES, status),
class: "inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium #{colors[status] || 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200'}"
end
def mailing_open_badge(recipient)
return unless recipient.status == "sent"
if recipient.opened?
content_tag :span, "Aperta",
class: "inline-flex items-center rounded-md bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-800 dark:bg-violet-950 dark:text-violet-200",
title: "Prima apertura #{format_dt(recipient.opened_at)}"
else
content_tag :span, "Non aperta",
class: "inline-flex items-center rounded-md bg-zinc-100 px-2 py-0.5 text-xs font-medium text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300"
end
end
def mailing_recipient_delivery_note(recipient)
if recipient.status == "failed" && recipient.error_message.present?
content_tag :div, recipient.error_message, class: "mt-1 text-xs text-rose-700 dark:text-rose-300"
elsif recipient.queued? && recipient.error_message.to_s.match?(/Da ritentare/)
content_tag :div, "Non risulta inviata. Verrà ritentata in coda.",
class: "mt-1 text-xs text-amber-800 dark:text-amber-200"
end
end
def ab_variant_badge(variant)
return if variant.blank?
colors = {
"A" => "bg-indigo-100 text-indigo-800 dark:bg-indigo-950 dark:text-indigo-200",
"B" => "bg-fuchsia-100 text-fuchsia-800 dark:bg-fuchsia-950 dark:text-fuchsia-200"
}
content_tag :span, "Test #{variant}",
class: "inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium #{colors[variant] || 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200'}"
end
def sanitize_email_html(html)
sanitize(
html.to_s,
tags: %w[p br strong b em i u a ul ol li h1 h2 h3 h4 h5 blockquote pre hr img figure figcaption span div],
attributes: %w[href src alt style class width height]
)
end
def streaming_badge(status)
return if status.blank?
colors = {
"not_detected" => "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300",
"limited" => "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200",
"yes_partial" => "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-200",
"yes" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
"yes_sportcam" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200"
}
content_tag :span, Catalog.label_for(Catalog::STREAMING_STATUSES, status),
class: "inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium #{colors[status] || 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300'}"
end
def yes_no(value)
value ? "Sì" : "No"
end
def table_clip(text, length: 36)
return "—" if text.blank?
value = text.to_s.strip
label = value.length > length ? "#{value[0, length]}…" : value
tag.span label, title: value, class: "inline-block max-w-[14rem] truncate align-bottom"
end
def external_link(url, **options)
return "—" if url.blank?
href = url.match?(%r{\Ahttps?://}i) ? url : "https://#{url}"
link_to url, href, target: "_blank", rel: "noopener", **options
end
end