A Pen by Adriel Santos on CodePen.
Created
October 31, 2025 05:23
-
-
Save adrielcruz9966-a11y/1324ed4732f3a65514b1bad0baca7b8c to your computer and use it in GitHub Desktop.
Untitled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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