feat: implementacao inicial do sistema LeadRadar
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import streamlit as st
|
||||
from api_client import APIClient
|
||||
|
||||
STATUS_CONFIG = [
|
||||
('novo', 'Novo', '#58A6FF'),
|
||||
('contatado', 'Contatado', '#D29922'),
|
||||
('respondeu', 'Respondeu', '#A371F7'),
|
||||
('negociacao', 'Em Negociação', '#F0883E'),
|
||||
('ganho', 'Ganho', '#3FB950'),
|
||||
('perdido', 'Perdido', '#F85149')
|
||||
]
|
||||
|
||||
def render_kanban_board(leads: list, api_client: APIClient):
|
||||
cols = st.columns(len(STATUS_CONFIG))
|
||||
|
||||
# Agrupar leads por status
|
||||
leads_by_status = {key: [] for key, _, _ in STATUS_CONFIG}
|
||||
for l in leads:
|
||||
st_key = l.get('status_funil', 'novo')
|
||||
if st_key in leads_by_status:
|
||||
leads_by_status[st_key].append(l)
|
||||
|
||||
for idx, (status_key, status_label, color) in enumerate(STATUS_CONFIG):
|
||||
with cols[idx]:
|
||||
col_leads = leads_by_status[status_key]
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="kanban-col-header" style="border-bottom-color: {color};">
|
||||
<span>{status_label}</span>
|
||||
<span class="lead-badge" style="background-color: rgba(255,255,255,0.1); color: var(--text-primary);">
|
||||
{len(col_leads)}
|
||||
</span>
|
||||
</div>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
for lead in col_leads:
|
||||
lead_id = lead['id']
|
||||
nome = lead.get('nome_empresa', 'Empresa')
|
||||
ramo = lead.get('ramo_atividade', '')
|
||||
tel = lead.get('telefone', '')
|
||||
tel_san = lead.get('telefone_sanitizado', '')
|
||||
rating = lead.get('google_rating', 0.0)
|
||||
reviews = lead.get('total_avaliacoes', 0)
|
||||
maps_url = lead.get('google_maps_url', '')
|
||||
|
||||
rating_str = f"⭐ {rating:.1f} ({reviews})" if rating > 0 else "Sem avaliação"
|
||||
|
||||
# Container visual do Card
|
||||
with st.container(border=True):
|
||||
st.markdown(f"**{nome}**")
|
||||
st.caption(f"🏷️ {ramo} | 📍 {lead.get('bairro', '') or lead.get('cidade', '')}")
|
||||
st.caption(f"📊 {rating_str}")
|
||||
|
||||
if tel_san:
|
||||
st.markdown(
|
||||
f'<a href="https://wa.me/{tel_san}" target="_blank" class="whatsapp-link">💬 WhatsApp ({tel})</a>',
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
elif tel:
|
||||
st.caption(f"📞 {tel}")
|
||||
|
||||
if maps_url:
|
||||
st.markdown(f"[📍 Abrir no Google Maps]({maps_url})")
|
||||
|
||||
c1, c2 = st.columns([2, 1])
|
||||
with c1:
|
||||
opts = [s[1] for s in STATUS_CONFIG]
|
||||
cur_idx = [s[0] for s in STATUS_CONFIG].index(status_key)
|
||||
new_label = st.selectbox(
|
||||
"Mover para:",
|
||||
opts,
|
||||
index=cur_idx,
|
||||
key=f"st_sel_{lead_id}",
|
||||
label_visibility="collapsed"
|
||||
)
|
||||
new_st_key = [s[0] for s in STATUS_CONFIG][opts.index(new_label)]
|
||||
if new_st_key != status_key:
|
||||
api_client.update_lead(lead_id, {'status_funil': new_st_key})
|
||||
st.rerun()
|
||||
|
||||
with c2:
|
||||
if st.button("📝", key=f"btn_notes_{lead_id}", help="Editar notas e ver histórico"):
|
||||
st.session_state['selected_lead_id'] = lead_id
|
||||
st.rerun()
|
||||
@@ -0,0 +1,61 @@
|
||||
import streamlit as st
|
||||
|
||||
def render_metrics_summary(leads: list):
|
||||
total = len(leads)
|
||||
novos = sum(1 for l in leads if l.get('status_funil') == 'novo')
|
||||
contatados = sum(1 for l in leads if l.get('status_funil') == 'contatado')
|
||||
negociacao = sum(1 for l in leads if l.get('status_funil') == 'negociacao')
|
||||
ganhos = sum(1 for l in leads if l.get('status_funil') == 'ganho')
|
||||
taxa_conversao = (ganhos / total * 100) if total > 0 else 0.0
|
||||
|
||||
c1, c2, c3, c4, c5 = st.columns(5)
|
||||
|
||||
with c1:
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="metric-card">
|
||||
<div class="metric-title">Total de Leads</div>
|
||||
<div class="metric-value">{total}</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
with c2:
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="metric-card">
|
||||
<div class="metric-title">Leads Novos</div>
|
||||
<div class="metric-value" style="color: #58A6FF;">{novos}</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
with c3:
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="metric-card">
|
||||
<div class="metric-title">Em Negociação</div>
|
||||
<div class="metric-value" style="color: #F0883E;">{negociacao}</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
with c4:
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="metric-card">
|
||||
<div class="metric-title">Ganhos</div>
|
||||
<div class="metric-value" style="color: #3FB950;">{ganhos}</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
with c5:
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="metric-card">
|
||||
<div class="metric-title">Taxa Conversão</div>
|
||||
<div class="metric-value" style="color: #A371F7;">{taxa_conversao:.1f}%</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
Reference in New Issue
Block a user