Skip to content

Instantly share code, notes, and snippets.

@adrielcruz9966-a11y
Created October 31, 2025 05:23
Show Gist options
  • Select an option

  • Save adrielcruz9966-a11y/1324ed4732f3a65514b1bad0baca7b8c to your computer and use it in GitHub Desktop.

Select an option

Save adrielcruz9966-a11y/1324ed4732f3a65514b1bad0baca7b8c to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Safra CRM 2.1 — Premium</title>
<!-- Font + Icons + Chart.js -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;500;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-V...==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
:root{
--bg-1:#061427; /* deep navy */
--bg-2:#0a304f; /* safra grad */
--gold:#C5A559;
--glass: rgba(255,255,255,0.06);
--glass-2: rgba(255,255,255,0.10);
--accent:#00b894;
--muted: rgba(255,255,255,0.85);
}
*{box-sizing:border-box;font-family:'Poppins',Arial,Helvetica,sans-serif}
html,body{height:100%;margin:0}
body{
background: linear-gradient(135deg,var(--bg-1),var(--bg-2));
color:#fff;padding:28px;
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
/* Header */
.header {
display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px;
}
.brand {
display:flex;gap:12px;align-items:center;
}
.logo {
width:56px;height:56px;border-radius:12px;
background: linear-gradient(180deg,var(--bg-2),var(--bg-1));
display:flex;align-items:center;justify-content:center;border:2px solid rgba(197,165,89,0.9);
box-shadow: 0 8px 26px rgba(0,0,0,0.5);
}
.logo strong{color:var(--gold);font-size:18px}
.title h1{margin:0;color:var(--gold);letter-spacing:0.6px;font-size:18px}
.title p{margin:0;font-size:12px;color:rgba(255,255,255,0.8)}
/* layout */
.container{display:grid;grid-template-columns:360px 1fr;gap:20px;align-items:start}
@media(max-width:980px){.container{grid-template-columns:1fr;padding-bottom:100px}}
/* FORM PANEL (glass) */
.panel {
background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.02));
border: 1px solid rgba(197,165,89,0.12);
border-radius:14px;padding:18px;box-shadow: 0 8px 30px rgba(0,0,0,0.45);
position:relative;overflow:hidden;
transition: transform .28s;
}
.panel h2{color:var(--gold);font-size:16px;margin-bottom:8px}
.input, textarea, select{
width:100%;padding:10px;border-radius:10px;border:1px solid rgba(255,255,255,0.06);
background:transparent;color:#fff;margin-bottom:10px;font-size:14px;outline:none;
}
.input:focus,select:focus,textarea:focus{box-shadow: 0 0 0 6px rgba(197,165,89,0.06);border-color:var(--gold)}
.row{display:flex;gap:8px}
.btn-primary{
width:100%;display:inline-block;padding:11px;border-radius:10px;border:none;
background: linear-gradient(90deg,var(--gold), #e0b74a); color:var(--bg-1); font-weight:800;cursor:pointer;
margin-top:6px;box-shadow: 0 8px 20px rgba(197,165,89,0.12);
}
.small{display:flex;gap:8px;align-items:center;margin-top:6px}
.link-btn{background:transparent;border:1px dashed rgba(255,255,255,0.06);padding:8px;border-radius:8px;color:var(--gold);cursor:pointer}
/* floating + button */
.fab {
position: fixed; right: 24px; bottom: 24px; width:64px; height:64px;border-radius:50%;
background: linear-gradient(180deg,var(--gold),#e0b74a); color:var(--bg-1); display:flex;align-items:center;justify-content:center;
font-size:24px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); cursor:pointer; border:none; z-index:60;
transition: transform .18s, box-shadow .18s;
}
.fab:hover{transform:translateY(-6px);box-shadow:0 18px 36px rgba(0,0,0,0.55)}
/* metrics top */
.metrics {
grid-column:1/-1;margin-bottom:14px;display:flex;gap:12px;align-items:center;flex-wrap:wrap;
}
.metric {
min-width:120px;padding:12px 14px;border-radius:12px;background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.02));
border:1px solid rgba(255,255,255,0.04); display:flex;flex-direction:column;gap:6px;
}
.metric .label{font-size:12px;color:var(--gold);font-weight:700}
.metric .value{font-size:18px;font-weight:800;color:#fff}
.metric:hover{transform:translateY(-6px);transition:transform .18s}
/* main area */
.main {display:flex;flex-direction:column;gap:12px}
.controls{display:flex;gap:10px;align-items:center}
#filtro{flex:1;padding:10px;border-radius:10px;border:2px solid rgba(197,165,89,0.12);background:transparent;color:#fff}
/* table card */
.table-wrap {
background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
border-radius:12px;padding:10px;border:1px solid rgba(255,255,255,0.04);
box-shadow: 0 8px 30px rgba(0,0,0,0.45); overflow:auto;
}
table{width:100%;border-collapse:collapse;color:#fff}
th,td{padding:12px;text-align:left;border-bottom:1px solid rgba(255,255,255,0.03);font-size:14px}
th{background:linear-gradient(180deg,var(--bg-2),var(--bg-1));color:var(--gold);position:sticky;top:0}
tr:hover td{background:rgba(255,255,255,0.02)}
.actions{display:flex;gap:8px}
/* icons */
.icon-btn{padding:8px 10px;border-radius:8px;border:none;cursor:pointer;color:#fff;font-weight:700}
.icon-wh{background:linear-gradient(90deg,#25D366,#1DA851)}
.icon-edit{background:linear-gradient(90deg,#58a6ff,#2b7be4)}
.icon-del{background:linear-gradient(90deg,#ff6b6b,#f04242)}
/* status tag */
.tag{display:inline-block;padding:6px 10px;border-radius:10px;font-weight:800;color:#fff}
.tag.quente{background:linear-gradient(90deg,#ff6b6b,#ff3b3b)}
.tag.morno{background:linear-gradient(90deg,#f7a63b,#f39c12)}
.tag.frio{background:linear-gradient(90deg,#58a6ff,#2b9bff)}
.tag.link{background:linear-gradient(90deg,#b67cff,#9b59b6)}
/* follow-up badge */
.badge{font-size:12px;padding:4px 8px;border-radius:10px;background:rgba(255,255,255,0.04);color:var(--gold);font-weight:800}
/* chart card */
.card {background:transparent;border-radius:10px;padding:8px}
/* motivational banner */
.motiv {
grid-column:1/-1;margin-bottom:12px;background:linear-gradient(90deg, rgba(255,255,255,0.03), rgba(255,255,255,0.02));
border-radius:12px;padding:12px;border:1px solid rgba(255,255,255,0.04);display:flex;align-items:center;gap:12px;overflow:hidden;
}
.m-text{font-weight:700;color:var(--gold);font-size:15px}
.m-flip{display:inline-block;padding:6px 10px;background:rgba(255,255,255,0.03);border-radius:8px;color:#fff;animation:slideText 7s linear infinite}
@keyframes slideText{
0%{transform:translateX(0);opacity:1}
25%{transform:translateX(-100%);opacity:0}
50%{transform:translateX(100%);opacity:0}
75%{transform:translateX(0);opacity:1}
100%{transform:translateX(0);opacity:1}
}
/* small animations */
.fade-in{animation:fadeIn .38s ease forwards}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.slide-up{animation:slideUp .28s ease forwards}
@keyframes slideUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
/* flame animation (row) */
@keyframes fogo{0%{transform:translateY(0)}50%{transform:translateY(-6px)}100%{transform:translateY(0)}}
.row-flame{position:absolute;right:12px;top:8px;font-size:16px;animation:fogo 1s infinite}
/* responsive */
@media(max-width:980px){
.logo{width:44px;height:44px}
.container{grid-template-columns:1fr}
.panel{order:2}
.main{order:1}
}
</style>
</head>
<body>
<!-- HEADER -->
<div class="header">
<div class="brand">
<div class="logo"><strong>SF</strong></div>
<div class="title">
<h1>Safra CRM — Premium</h1>
<p>Correspondente Bancário • Painel rápido e responsivo</p>
</div>
</div>
<div style="display:flex;gap:10px;align-items:center">
<button id="exportBtn" class="link-btn" title="Exportar CSV"><i class="fa fa-file-csv"></i> Exportar</button>
<button id="clearBtn" class="link-btn" title="Limpar local">Limpar</button>
</div>
</div>
<!-- MOTIVATIONAL ROTATOR -->
<div class="motiv">
<div style="font-size:20px;color:var(--gold)"><i class="fa fa-bolt"></i></div>
<div>
<div style="font-size:13px;color:rgba(255,255,255,0.8)">Motivação</div>
<div class="m-text"><span id="motivationText" class="m-flip">Acredite: cada ligação é uma oportunidade.</span></div>
</div>
</div>
<!-- METRICS -->
<div class="metrics">
<div class="metric"><div class="label">🔥 Quentes</div><div class="value" id="contQuente">0</div></div>
<div class="metric"><div class="label">💨 Mornos</div><div class="value" id="contMorno">0</div></div>
<div class="metric"><div class="label">❄ Frios</div><div class="value" id="contFrio">0</div></div>
<div class="metric"><div class="label">🔗 Links</div><div class="value" id="contLink">0</div></div>
<div class="metric" style="margin-left:auto"><div class="label">📊 Total</div><div class="value" id="contTotal">0</div></div>
<div class="metric goal" style="background:linear-gradient(90deg,var(--gold),#e0b74a);color:var(--bg-1)"><div class="label">Progresso</div><div style="font-weight:900" id="goalPercent">0%</div></div>
</div>
<div class="container">
<!-- LEFT: FORM PANEL -->
<div class="panel" id="formPanel">
<h2 id="formTitle">Adicionar novo lead</h2>
<form id="clienteForm">
<input id="nome" class="input" placeholder="Nome completo" required />
<div class="row">
<input id="cpf" class="input" placeholder="CPF (apenas números)" />
<input id="telefone" class="input" placeholder="Telefone (com DDD)" />
</div>
<select id="status" class="input" required>
<option value="">Status do lead</option>
<option value="Quente">Quente 🔥</option>
<option value="Morno">Morno 💨</option>
<option value="Frio">Frio ❄</option>
<option value="Link">Link em aberto 🔗</option>
</select>
<textarea id="observacao" rows="3" placeholder="Observações / estratégia" class="input"></textarea>
<div class="small">
<label style="color:var(--gold);font-weight:700;font-size:13px">Follow-up</label>
<input id="followup" type="date" />
<button id="whTemplate" type="button" class="link-btn" style="margin-left:auto">Modelo WA</button>
</div>
<input id="leadId" type="hidden" />
<button id="saveBtn" class="btn-primary" type="submit">Adicionar Cliente</button>
<div style="margin-top:12px;font-size:13px;color:rgba(255,255,255,0.85)">
Dica: use "Editar" para atualizar um lead. Dados salvos no navegador.
</div>
</form>
<hr style="border:none;height:1px;background:rgba(255,255,255,0.03);margin:12px 0;border-radius:2px">
<div>
<div style="color:var(--gold);font-weight:800;margin-bottom:6px">Meta diária</div>
<div style="display:flex;gap:8px">
<input id="dailyGoal" type="number" min="1" placeholder="Meta ex: 5" class="input" />
<button id="setGoal" class="link-btn">Salvar</button>
</div>
<div class="progress" style="height:10px;background:rgba(255,255,255,0.04);border-radius:8px;margin-top:8px;overflow:hidden">
<i id="goalBar" style="display:block;height:100%;width:0%;background:linear-gradient(90deg,var(--gold),#f0c400)"></i>
</div>
<div style="display:flex;gap:10px;margin-top:8px;font-size:13px;color:rgba(255,255,255,0.9)">
<div>Concluídos: <strong id="goalDone">0</strong></div>
<div>Meta: <strong id="goalTotal">0</strong></div>
</div>
</div>
</div>
<!-- RIGHT: MAIN -->
<div class="main">
<div class="controls">
<input id="filtro" placeholder="Buscar por nome, status, telefone..." />
<div class="card" style="width:260px">
<canvas id="chart" width="260" height="80"></canvas>
</div>
</div>
<div class="table-wrap">
<table id="clientesTabela" aria-describedby="lista de leads">
<thead>
<tr>
<th>Nome</th><th>CPF</th><th>Telefone</th><th>Status</th><th>Follow-up</th><th>Observações</th><th>Ações</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="reminders"></div>
</div>
</div>
<!-- Floating add button -->
<button class="fab" id="fabBtn" title="Novo lead">+</button>
<script>
/* ---------- CONFIG / STORAGE ---------- */
const STORAGE_KEY = 'safra_crm_v2.1';
const GOAL_KEY = 'safra_goal_v2.1';
function uid(){ return Date.now().toString(36) + Math.random().toString(36).slice(2,5) }
/* ---------- STATE ---------- */
let leads = [];
let chart = null;
const frases = [
"Você é capaz de conquistar qualquer meta!",
"Cada ligação é uma oportunidade de sucesso!",
"Foque no cliente, o fechamento vem naturalmente!",
"Confiança é sua maior arma de vendas!",
"Persistência vence resistência!",
"Hoje é o dia de transformar leads em clientes!",
"Seus resultados refletem sua dedicação!"
];
/* ---------- DOM ---------- */
const tabelaBody = document.querySelector('#clientesTabela tbody');
const form = document.getElementById('clienteForm');
const nomeI = document.getElementById('nome');
const cpfI = document.getElementById('cpf');
const telI = document.getElementById('telefone');
const statusI = document.getElementById('status');
const obsI = document.getElementById('observacao');
const followI = document.getElementById('followup');
const leadIdI = document.getElementById('leadId');
const filtroI = document.getElementById('filtro');
const contQuente = document.getElementById('contQuente');
const contMorno = document.getElementById('contMorno');
const contFrio = document.getElementById('contFrio');
const contLink = document.getElementById('contLink');
const contTotal = document.getElementById('contTotal');
const remindersDiv = document.getElementById('reminders');
const dailyGoalI = document.getElementById('dailyGoal');
const setGoalBtn = document.getElementById('setGoal');
const goalBar = document.getElementById('goalBar');
const goalDone = document.getElementById('goalDone');
const goalTotal = document.getElementById('goalTotal');
const goalPercent = document.getElementById('goalPercent');
const motivationText = document.getElementById('motivationText');
const fabBtn = document.getElementById('fabBtn');
const formPanel = document.getElementById('formPanel');
const saveBtn = document.getElementById('saveBtn');
const exportBtn = document.getElementById('exportBtn');
const clearBtn = document.getElementById('clearBtn');
const whTemplateBtn = document.getElementById('whTemplate');
/* ---------- PERSISTENCE ---------- */
function load(){
const raw = localStorage.getItem(STORAGE_KEY);
leads = raw ? JSON.parse(raw) : [];
const g = localStorage.getItem(GOAL_KEY);
if(g){ const o = JSON.parse(g); dailyGoalI.value = o.goal || ''; goalTotal.textContent = o.goal || 0; }
}
function save(){ localStorage.setItem(STORAGE_KEY, JSON.stringify(leads)); }
/* ---------- RENDER ---------- */
function render(){
tabelaBody.innerHTML = '';
leads.forEach(l => {
const tr = document.createElement('tr');
// Nome
const ntd = document.createElement('td'); ntd.textContent = l.nome || '-'; tr.appendChild(ntd);
// CPF
const ctd = document.createElement('td'); ctd.textContent = l.cpf || '-'; tr.appendChild(ctd);
// Tel
const ttd = document.createElement('td'); ttd.textContent = l.telefone || '-'; tr.appendChild(ttd);
// Status
const std = document.createElement('td');
const tag = document.createElement('span'); tag.className = 'tag ' + (l.status==='Quente'?'quente':l.status==='Morno'?'morno':l.status==='Frio'?'frio':'link');
tag.textContent = `${l.status} ${l.status==='Quente'?'🔥':l.status==='Morno'?'💨':l.status==='Frio'?'❄':'🔗'}`;
std.appendChild(tag); tr.appendChild(std);
// Followup
const fud = document.createElement('td');
if(l.followup){
const d = new Date(l.followup);
fud.innerHTML = `<span class="badge">${d.toLocaleDateString()}</span>`;
const today = new Date(); today.setHours(0,0,0,0);
const fday = new Date(l.followup); fday.setHours(0,0,0,0);
if(fday.getTime() === today.getTime()){
const r = document.createElement('span'); r.className='badge'; r.style.marginLeft='8px'; r.style.background='linear-gradient(90deg,#ffdf32,#f5c400)'; r.textContent='HOJE'; fud.appendChild(r);
} else if(fday < today){
const r = document.createElement('span'); r.className='badge'; r.style.marginLeft='8px'; r.style.background='rgba(255,80,80,0.12)'; r.textContent='ATRASADO'; fud.appendChild(r);
}
} else { fud.textContent='-'; }
tr.appendChild(fud);
// Observação
const otd = document.createElement('td'); otd.textContent = l.observacao || '-'; tr.appendChild(otd);
// Ações
const ac = document.createElement('td'); ac.className='actions';
// WA
const wh = document.createElement('button'); wh.className='icon-btn icon-wh'; wh.title='WhatsApp'; wh.innerHTML='WA';
wh.onclick = ()=> openWhatsApp(l);
ac.appendChild(wh);
// Edit
const ed = document.createElement('button'); ed.className='icon-btn icon-edit'; ed.title='Editar'; ed.innerHTML='✎';
ed.onclick = ()=> loadToForm(l.id);
ac.appendChild(ed);
// Del
const dl = document.createElement('button'); dl.className='icon-btn icon-del'; dl.title='Apagar'; dl.innerHTML='🗑';
dl.onclick = ()=> { if(confirm('Apagar "'+l.nome+'"?')) { leads = leads.filter(x=>x.id!==l.id); save(); render(); updateMetrics(); flashMessage('Lead removido'); } };
ac.appendChild(dl);
tr.appendChild(ac);
tabelaBody.appendChild(tr);
// Flame icon
if(l.status === 'Quente'){
const flame = document.createElement('span'); flame.className='row-flame'; flame.textContent='🔥';
tr.style.position='relative'; tr.appendChild(flame);
}
});
updateMetrics();
renderChart();
renderReminders();
}
/* ---------- METRICS ---------- */
function updateMetrics(){
const q = leads.filter(l=>l.status==='Quente').length;
const m = leads.filter(l=>l.status==='Morno').length;
const f = leads.filter(l=>l.status==='Frio').length;
const li = leads.filter(l=>l.status==='Link').length;
contQuente.textContent = q; contMorno.textContent = m; contFrio.textContent = f; contLink.textContent = li; contTotal.textContent = leads.length;
// goal progress: use leads created today
const gobj = JSON.parse(localStorage.getItem(GOAL_KEY) || '{}');
const goal = gobj.goal || 0;
const today = new Date(); today.setHours(0,0,0,0);
const done = leads.filter(l => { const c = new Date(l.createdAt); c.setHours(0,0,0,0); return c.getTime() === today.getTime(); }).length;
goalDone.textContent = done; goalTotal.textContent = goal;
const percent = goal ? Math.min(100, Math.round((done/goal)*100)) : 0;
goalBar.style.width = percent + '%';
goalPercent.textContent = percent + '%';
}
/* ---------- CHART ---------- */
function renderChart(){
const counts = {
Quente: leads.filter(l=>l.status==='Quente').length,
Morno: leads.filter(l=>l.status==='Morno').length,
Frio: leads.filter(l=>l.status==='Frio').length,
Link: leads.filter(l=>l.status==='Link').length
};
const ctx = document.getElementById('chart');
const data = {
labels: ['Quente','Morno','Frio','Link'],
datasets: [{data:[counts.Quente,counts.Morno,counts.Frio,counts.Link], backgroundColor:['#ff6b6b','#f7a63b','#58a6ff','#b67cff'], borderWidth:0}]
};
if(chart){ chart.data = data; chart.update(); return; }
chart = new Chart(ctx, { type:'doughnut', data, options:{plugins:{legend:{display:false}}, cutout:'70%'} });
}
/* ---------- REMINDERS ---------- */
function renderReminders(){
remindersDiv.innerHTML = '';
const today = new Date(); today.setHours(0,0,0,0);
const due = leads.filter(l => l.followup).filter(l=>{ const f = new Date(l.followup); f.setHours(0,0,0,0); return f.getTime() <= today.getTime() });
if(due.length){
const wrap = document.createElement('div'); wrap.style.marginTop='8px';
wrap.innerHTML = `<div style="padding:12px;border-radius:10px;background:linear-gradient(90deg,#ffdf32,#f5c400);color:#03203b;font-weight:900">⚠️ Follow-ups hoje/atrasados: ${due.length}</div>`;
remindersDiv.appendChild(wrap);
}
}
/* ---------- ACTIONS: ADD / EDIT ---------- */
form.addEventListener('submit', e=>{
e.preventDefault();
const id = leadIdI.value;
const payload = {
id: id || uid(),
nome: nomeI.value.trim(),
cpf: cpfI.value.trim(),
telefone: telI.value.trim(),
status: statusI.value,
observacao: obsI.value.trim(),
followup: followI.value || '',
createdAt: id ? (leads.find(x=>x.id===id)?.createdAt || new Date().toISOString()) : new Date().toISOString()
};
if(!payload.nome){ alert('Nome é obrigatório'); return; }
if(id){
leads = leads.map(l => l.id===id ? payload : l);
save(); render(); resetForm(); flashMessage('Lead atualizado');
} else {
leads.unshift(payload);
save(); render(); resetForm(); flashMessage('Lead adicionado');
}
});
function resetForm(){
form.reset(); leadIdI.value = ''; document.getElementById('formTitle').textContent = 'Adicionar novo lead'; saveBtn.textContent = 'Adicionar Cliente';
}
/* load to form */
function loadToForm(id){
const l = leads.find(x=>x.id===id); if(!l) return;
nomeI.value = l.nome; cpfI.value = l.cpf; telI.value = l.telefone; statusI.value = l.status; obsI.value = l.observacao || ''; followI.value = l.followup || ''; leadIdI.value = l.id;
document.getElementById('formTitle').textContent = 'Editar lead'; saveBtn.textContent = 'Salvar alteração';
window.scrollTo({top:0,behavior:'smooth'});
}
/* ---------- WHATSAPP ---------- */
function openWhatsApp(l){
const template = encodeURIComponent(`Olá ${l.nome}, tudo bem? Sou seu correspondente. Gostaria de falar sobre sua simulação/atendimento. Podemos agendar?`);
const phone = (l.telefone || '').replace(/\D/g,'');
const url = phone ? `https://wa.me/${phone}?text=${template}` : `https://wa.me/?text=${template}`;
window.open(url,'_blank');
}
/* ---------- FILTER ---------- */
filtroI.addEventListener('input', ()=>{
const q = filtroI.value.toLowerCase();
[...tabelaBody.rows].forEach(r=>{
const txt = r.innerText.toLowerCase();
r.style.display = txt.includes(q) ? '' : 'none';
});
});
/* ---------- GOAL ---------- */
setGoalBtn.addEventListener('click', ()=>{
const g = Math.max(0, Number(dailyGoalI.value || 0));
localStorage.setItem(GOAL_KEY, JSON.stringify({goal:g}));
goalTotal.textContent = g;
updateMetrics();
flashMessage('Meta salva: ' + g);
});
/* ---------- EXPORT CSV ---------- */
exportBtn.addEventListener('click', ()=>{
if(leads.length===0){ alert('Nenhum lead para exportar'); return; }
const rows = [['Nome','CPF','Telefone','Status','Follow-up','Observações','Criado em']];
leads.forEach(l => rows.push([l.nome,l.cpf,l.telefone,l.status,l.followup || '', l.observacao || '', l.createdAt]));
const csv = rows.map(r=>r.map(c=>`"${(c||'').toString().replace(/"/g,'""')}"`).join(',')).join('\n');
const blob = new Blob([csv],{type:'text/csv;charset=utf-8;'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = 'leads_safra_v2.1.csv'; a.click(); URL.revokeObjectURL(url);
});
/* ---------- CLEAR ALL ---------- */
clearBtn.addEventListener('click', ()=>{
if(confirm('Limpar todos os dados localmente?')){ leads=[]; save(); render(); flashMessage('Dados limpos'); }
});
/* ---------- FAB (open/close form) ---------- */
let panelVisible = true;
fabBtn.addEventListener('click', ()=>{
panelVisible = !panelVisible;
if(panelVisible){
formPanel.style.transform = 'translateY(0)'; formPanel.style.opacity = 1;
fabBtn.textContent = '+';
window.scrollTo({top:0,behavior:'smooth'});
} else {
formPanel.style.transform = 'translateY(20px)'; formPanel.style.opacity = 0.06;
fabBtn.textContent = '✕';
}
});
/* ---------- TEMPLATE WA BUTTON ---------- */
whTemplateBtn.addEventListener('click', ()=> alert('Modelo WA:\n\n"Olá [NOME], aqui é seu correspondente do Safra. Posso te ligar para finalizar sua simulação? Obrigado!"'));
/* ---------- UTILS ---------- */
function flashMessage(msg){
const el = document.createElement('div'); el.textContent = msg;
el.style.position='fixed'; el.style.right='20px'; el.style.bottom='100px'; el.style.background='linear-gradient(90deg,#ffd85e,#f0c400)'; el.style.color='#03203b'; el.style.padding='10px 14px'; el.style.borderRadius='10px'; el.style.fontWeight='800'; el.style.boxShadow='0 10px 30px rgba(0,0,0,0.4)'; el.style.zIndex='9999';
document.body.appendChild(el); setTimeout(()=> el.style.opacity='0',1600); setTimeout(()=> el.remove(),2100);
}
/* ---------- MOTIVATIONAL ROTATOR ---------- */
let motIndex = 0;
function rotateMotivation(){
motIndex = (motIndex + 1) % frases.length;
motivationText.classList.remove('m-flip');
void motivationText.offsetWidth;
motivationText.textContent = frases[motIndex];
motivationText.classList.add('m-flip');
}
setInterval(rotateMotivation, 5000);
/* ---------- INIT ---------- */
load(); render(); updateMetrics();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment