/* ============================================================ KeS Metas — Telas administrativas Usuários (CRUD) · Configurar Metas · Visão Consolidada ============================================================ */ const { useState } = React; /* ============================================================ USUÁRIOS (CRUD — somente admin) ============================================================ */ function UserModal({ initial, onClose, onSave, emails, onReset }) { const editing = !!initial; const usaFirebase = !!(window.KESAUTH && window.KESAUTH.configured()); const [f, setF] = useState(initial || { nome: "", email: "", senha: "", papel: "comum" }); const [err, setErr] = useState(""); const [busy, setBusy] = useState(false); const set = (k, v) => setF((s) => ({ ...s, [k]: v })); function save() { if (!f.nome.trim()) return setErr("Informe o nome."); if (!/^\S+@\S+\.\S+$/.test(f.email)) return setErr("E-mail inválido."); const dup = emails.some((e) => e.email.toLowerCase() === f.email.toLowerCase() && e.id !== (initial && initial.id)); if (dup) return setErr("Este e-mail já está cadastrado."); if (!editing && f.senha.length < 6) return setErr("Defina uma senha inicial (mín. 6 caracteres)."); setBusy(true); Promise.resolve(onSave(f)).then((r) => { setBusy(false); if (r && r.error) setErr(r.error); else onClose(); }); } return ( Cancelar{busy ? "Salvando…" : (editing ? "Salvar" : "Cadastrar")}}> set("nome", e.target.value)} placeholder="Nome e sobrenome" /> set("email", e.target.value)} placeholder="pessoa@kesassessoria.com.br" /> {!editing && (
set("senha", e.target.value)} placeholder="mín. 6 caracteres" />
)} {editing && ( )} {editing && usaFirebase && onReset && (
Enviar um link de redefinição de senha para o e-mail da pessoa.
onReset(f.email)}>Redefinir senha
)} {err &&
{err}
}

{f.papel === "admin" ? "Administradores veem e editam os dados de toda a equipe." : "Usuários comuns veem e editam apenas os próprios dados."} {!editing && usaFirebase ? " A conta de acesso é criada com segurança no Google." : ""}

); } function UsuariosScreen({ state, dispatch, viewer }) { const [modal, setModal] = useState(null); // null | {} | user const [del, setDel] = useState(null); const A = window.KESAUTH; const usaFirebase = !!(A && A.configured()); // Retorna {error} para o modal exibir, ou cria/atualiza e fecha. async function save(f) { if (modal && modal.id) { dispatch({ type: "UPDATE_USER", id: modal.id, patch: { nome: f.nome, papel: f.papel } }); dispatch({ type: "TOAST", msg: "Usuário atualizado." }); setModal(null); return {}; } // Novo usuário: cria a conta de login no Firebase e adiciona ao sistema. if (usaFirebase) { const res = await A.createUser(f.email, f.senha); if (!res.ok) return { error: A.traduzErro(res) }; dispatch({ type: "ADD_USER", user: { id: res.uid, nome: f.nome, email: f.email, papel: f.papel } }); } else { dispatch({ type: "ADD_USER", user: { nome: f.nome, email: f.email, papel: f.papel } }); } dispatch({ type: "TOAST", msg: "Usuário cadastrado." }); setModal(null); return {}; } async function resetar(email) { if (!usaFirebase) return; const res = await A.resetPassword(email); dispatch({ type: "TOAST", msg: res.ok ? "Link de redefinição enviado para " + email + "." : A.traduzErro(res) }); } return (

Usuários

Cadastre, edite e remova membros da equipe. Apenas administradores têm acesso a esta tela.

setModal({})}>Novo usuário
{state.users.map((u) => ( ))}
UsuárioPapelLeads atribuídosAções
{u.nome}{u.id === viewer.id ? " (você)" : ""}
{u.email}
{u.papel === "admin" ? "Administrador" : "Comum"} {state.leads.filter((l) => l.responsavelId === u.id).length} leads
{modal && setModal(null)} onSave={save} onReset={resetar} />} {del && ( setDel(null)} footer={<> setDel(null)}>Cancelar { dispatch({ type: "DELETE_USER", id: del.id }); dispatch({ type: "TOAST", msg: "Usuário excluído." }); setDel(null); }}>Excluir}>

Tem certeza que deseja excluir {del.nome}? Todos os dados de atividade e agendamentos desse usuário serão removidos. Esta ação não pode ser desfeita.

)}
); } /* ============================================================ CONFIGURAR METAS (somente admin) ============================================================ */ function ConfigurarMetasScreen({ state, dispatch }) { const K = window.KES; const [m, setM] = useState(() => JSON.parse(JSON.stringify(state.metas))); const setAlvo = (key, v) => setM((s) => ({ ...s, [key]: { ...s[key], alvo: v } })); const dirty = JSON.stringify(m) !== JSON.stringify(state.metas); function save() { const clean = JSON.parse(JSON.stringify(m)); Object.keys(clean).forEach((k) => { clean[k].alvo = Math.max(0, parseInt(clean[k].alvo, 10) || 0); }); dispatch({ type: "SET_METAS", metas: clean }); dispatch({ type: "TOAST", msg: "Metas atualizadas para toda a equipe." }); } const rows = [ { key: "ligacoes", icon: "phone", desc: "Quantidade de ligações esperada por dia útil." }, { key: "conexoes", icon: "link", desc: "Conexões/contatos efetivos por dia útil." }, { key: "reunioesAgendadas", icon: "calendarCheck", desc: "Reuniões marcadas por dia útil." }, { key: "reunioesRealizadas", icon: "handshake", desc: "Reuniões efetivamente realizadas por dia útil." }, { key: "reunioesQualificadas", icon: "checkCircle", desc: "Reuniões qualificadas (lead com fit) por dia útil." }, { key: "vendas", icon: "trophy", desc: "Vendas fechadas por mês, por pessoa." }, { key: "valorVendas", icon: "banknote", desc: "Valor (R$) em vendas por mês, por pessoa." }, ]; return (

Configurar Metas

Defina os alvos da equipe. Valem para todos os usuários e recalculam automaticamente conforme o período filtrado.

{dirty ? "Salvar metas" : "Tudo salvo"}
{rows.map((r, i) => (
{state.metas[r.key].rotulo}
{r.desc}
setAlvo(r.key, e.target.value)} /> / {m[r.key].unidade === "dia" ? "dia" : "mês"}
))}
); } /* ============================================================ VISÃO CONSOLIDADA (somente admin) ============================================================ */ function pctMini(pct) { const tone = window.tonePct(pct); return (
{Math.round(pct)}%
); } function ConsolidadoScreen({ state, dispatch, viewer }) { const K = window.KES, KM = window.KESM; const team = state.users; const ids = team.map((u) => u.id); const totals = KM.teamMetrics(state, ids, state.period); const rows = team.map((u) => { const mtr = KM.userMetrics(state, u.id, state.period); return { u, mtr, overall: KM.overallPct(mtr) }; }).sort((a, b) => b.overall - a.overall); const top = rows[0]; const cols = [ { key: "ligacoes", lbl: "Ligações" }, { key: "conexoes", lbl: "Conexões" }, { key: "reunioesAgendadas", lbl: "Reun. agend." }, { key: "reunioesRealizadas", lbl: "Reun. realiz." }, { key: "reunioesQualificadas", lbl: "Reun. qualif." }, { key: "vendas", lbl: "Vendas" }, ]; return (

Visão da Equipe

Comparativo de todos os usuários no período. Clique em uma linha para ver o detalhe individual.

dispatch({ type: "SET_PERIOD", period: p })} />
{/* Destaques */}
Destaque
Maior atingimento
{top && }
{top ? top.u.nome.split(" ")[0] : "—"}
{top ? Math.round(top.overall) + "% da meta" : ""}
Equipe
Vendas no período
{K.fmtNum(totals.vendas.realizado)}
meta {K.fmtNum(totals.vendas.meta)} · {Math.round(totals.vendas.pct)}%
Equipe
Valor em vendas
{K.fmtMoeda(totals.valorVendas.realizado)}
meta {K.fmtMoeda(totals.valorVendas.meta)}
Comparativo por usuário
{cols.map((c) => )} {rows.map((r, i) => ( dispatch({ type: "OPEN_USER", userId: r.u.id })}> {cols.map((c) => ( ))} ))} {cols.map((c) => ( ))}
#Usuário{c.lbl}Atingimento
{i + 1}º
{r.u.nome}
{r.u.papel === "admin" ? "Administrador" : "Comum"}
{K.fmtNum(r.mtr[c.key].realizado)}
/ {K.fmtNum(r.mtr[c.key].meta)}
{pctMini(r.overall)}
Totais da equipe {K.fmtNum(totals[c.key].realizado)}
/ {K.fmtNum(totals[c.key].meta)}
{pctMini(KM.overallPct(totals))}

Clique em qualquer usuário para abrir o painel individual dele.

); } window.UsuariosScreen = UsuariosScreen; window.ConfigurarMetasScreen = ConfigurarMetasScreen; window.ConsolidadoScreen = ConsolidadoScreen;