103 lines
5.2 KiB
Python
103 lines
5.2 KiB
Python
"""Dashboard and landing routes.
|
|
|
|
Moved from app.admin_dashboard in v4.7.2. The handlers still reuse
|
|
legacy helpers to keep this refactor behavior-preserving.
|
|
"""
|
|
from fastapi import APIRouter
|
|
import app.admin_dashboard as legacy
|
|
from app.admin_dashboard import * # noqa: F401,F403
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_class=HTMLResponse)
|
|
async def admin_home():
|
|
"""v4.5 clean Dashboard: visibility, not daily execution."""
|
|
metrics = get_admin_dashboard_metrics()
|
|
ops = get_operations_summary(limit=6)
|
|
counts = ops.get("counts") or {}
|
|
comms = get_communications_summary()
|
|
|
|
def n(key: str) -> int:
|
|
return int(metrics.get(key) or counts.get(key) or 0)
|
|
|
|
dashboard_cards = [
|
|
("Oportunidades abertas", counts.get("open_opportunities", 0), "/opportunities?status=open", "Negócio em acompanhamento"),
|
|
("Valor / documentos", counts.get("open_quotations", 0), "/finance", "Orçamentos abertos"),
|
|
("Tasks pendentes", n("pending_total"), "/operations", "Trabalho humano por resolver"),
|
|
("Mensagens a rever", comms.get("open", 0), "/operations", "Ações vindas do Chatwoot"),
|
|
("Erros de integração", counts.get("outbox_failed", 0), "/outbox?status=failed", "Jasmin/Packlink/outbox"),
|
|
("Pagamentos por confirmar", n("pending_financeiro"), "/operations", "Fila financeira"),
|
|
("Envios pendentes", counts.get("shipments_pending", 0), "/orders", "Logística/Packlink"),
|
|
("Clientes incompletos", counts.get("customers_incomplete", 0), "/customers", "Dados fiscais/morada"),
|
|
]
|
|
|
|
cards_html = ""
|
|
for label, value, href, hint in dashboard_cards:
|
|
cards_html += kpi_card(label, value, href, hint)
|
|
|
|
alert_items = []
|
|
if int(counts.get("outbox_failed") or 0):
|
|
alert_items.append(("Erro de integração", f"{counts.get('outbox_failed')} ação(ões) falhadas na outbox", "/outbox?status=failed", "cf-chip-red"))
|
|
if int(comms.get("needs_review") or 0):
|
|
alert_items.append(("Rever comunicação", f"{comms.get('needs_review')} mensagem(ns) com baixa confiança", "/operations", "cf-chip-orange"))
|
|
if int(counts.get("customers_incomplete") or 0):
|
|
alert_items.append(("Dados incompletos", f"{counts.get('customers_incomplete')} cliente(s) sem dados fiscais/morada completos", "/customers", "cf-chip-orange"))
|
|
if int(counts.get("products_missing_jasmin") or 0):
|
|
alert_items.append(("Produto bloqueante", f"{counts.get('products_missing_jasmin')} produto(s) ativos sem Artigo Jasmin", "/products?active=missing_jasmin", "cf-chip-red"))
|
|
|
|
alert_html = ""
|
|
for title, detail, href, chip in alert_items[:5]:
|
|
alert_html += f"""
|
|
<a class="d-flex justify-content-between align-items-start gap-3 py-3 border-bottom text-reset" href="{esc(href)}">
|
|
<div><span class="cf-chip {esc(chip)} mb-2">{esc(title)}</span><div class="fw-bold">{esc(detail)}</div></div>
|
|
<span class="text-primary fw-bold">Abrir →</span>
|
|
</a>
|
|
"""
|
|
if not alert_html:
|
|
alert_html = '<div class="text-secondary py-3">Sem alertas críticos neste momento.</div>'
|
|
|
|
body = f"""
|
|
<section class="alert alert-primary border-0 shadow-sm d-flex flex-wrap justify-content-between align-items-center gap-3">
|
|
<div>
|
|
<strong>Dashboard = visibilidade.</strong>
|
|
<span class="ms-1">O Chatwoot é a inbox. O ClientFlow mostra o trabalho, bloqueios e próximas ações.</span>
|
|
</div>
|
|
<a class="btn btn-primary" href="/operations">Abrir Centro de trabalho</a>
|
|
</section>
|
|
|
|
<section class="cf-kpi-grid">
|
|
{cards_html}
|
|
</section>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-xl-7">
|
|
<section class="card cf-card h-100">
|
|
<div class="card-body p-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div><h2 class="cf-section-title">Alertas</h2><div class="small text-secondary">Sinais globais que merecem atenção.</div></div>
|
|
<a class="btn btn-sm btn-outline-primary" href="/operations">Resolver no Centro de trabalho</a>
|
|
</div>
|
|
{alert_html}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
<div class="col-xl-5">
|
|
<section class="card cf-card h-100">
|
|
<div class="card-body p-4">
|
|
<h2 class="cf-section-title mb-3">Modelo operacional v4.5</h2>
|
|
<div class="d-grid gap-3">
|
|
<div class="cf-soft-box"><strong>Dashboard</strong><div class="small text-secondary">Mostra o estado e gargalos.</div></div>
|
|
<div class="cf-soft-box"><strong>Centro de trabalho</strong><div class="small text-secondary">Organiza o que precisa de ação agora.</div></div>
|
|
<div class="cf-soft-box"><strong>Chatwoot → ClientFlow</strong><div class="small text-secondary">O Chatwoot continua a ser a inbox; o ClientFlow transforma mensagens em ações, tasks e timeline.</div></div>
|
|
<div class="cf-soft-box"><strong>Oportunidade</strong><div class="small text-secondary">Mantém contexto, documentos, tasks, outbox e timeline.</div></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
"""
|
|
return layout("Dashboard", "Visão geral do negócio e do sistema", body, "overview")
|
|
|
|
|