/* ============================================================ KeS Metas — Chat interno por equipe ------------------------------------------------------------ Regras de visibilidade: - Cada equipe só conversa com a SUA equipe (mensagens ficam em equipes/{teamId}/chat — isolado por equipe). - "Geral": grupo com todos os membros da equipe (quem tem cargo). - "Individual": conversa 1-a-1 entre dois membros da equipe. - Admins (sem cargo) NÃO aparecem no chat e não participam. - Só o LÍDER da equipe tem o canal "Master" (fala com a KeS). Os demais membros não falam com o master. - O MASTER (dono/KeS) vê TODAS as conversas da equipe (monitorando) e conversa com o líder pelo canal "Master". Não aparece como contato para os membros comuns. ============================================================ */ function ChatScreen({ state, dispatch, viewer, isOwner }) { const { useState, useEffect, useRef, useMemo } = React; const DB = window.KESDB; // Tenant em foco: empresa (multiempresa) OU equipe — bancos separados. const teamId = tenantIdAtual(state); const souMaster = !!isOwner; // dono/KeS (papel owner, fora de pré-visualização) const souLider = viewer.cargo === "lider"; // Participantes do chat = membros com cargo (time de vendas). Admins/owner ficam de fora. const participantes = (state.users || []).filter((u) => u.cargo); const lider = participantes.find((u) => u.cargo === "lider") || null; const meuEmail = String(viewer.email || "").toLowerCase(); const MASTER_EMAIL = "kesassessoria@kesassessoria.com.br"; // conta mestra (lê tudo no banco) const [msgs, setMsgs] = useState([]); const [room, setRoom] = useState("geral"); const [texto, setTexto] = useState(""); const [threadAberta, setThreadAberta] = useState(false); // mobile: lista x conversa const [vistos, setVistos] = useState({}); // últimas leituras por sala (localStorage) const fimRef = useRef(null); const rotulo = (c) => (window.KESCOMISSAO && window.KESCOMISSAO.rotuloCargo ? window.KESCOMISSAO.rotuloCargo(c) : c); function chaveVistos() { return "keschat_vistos_" + (teamId || "x") + "_" + (souMaster ? "master" : viewer.id); } // Assina o chat da equipe atual (tempo real). Master lê tudo (monitorar); // membro lê só o Geral + as conversas em que ele participa (por e-mail). useEffect(() => { setMsgs([]); if (!DB || !DB.chatListen || !teamId) return; const opts = souMaster ? { all: true } : { email: meuEmail }; const unsub = DB.chatListen(teamId, opts, (lista) => setMsgs(lista || [])); return unsub; }, [teamId, souMaster, meuEmail]); // Carrega "vistos" da chave certa quando muda equipe/usuário useEffect(() => { try { setVistos(JSON.parse(localStorage.getItem(chaveVistos()) || "{}")); } catch (e) { setVistos({}); } }, [teamId, viewer.id, souMaster]); // Marca a sala atual como lida (guarda o ts da última mensagem) useEffect(() => { if (!room) return; const ult = msgs.filter((m) => m.room === room).reduce((a, m) => Math.max(a, m.ts || 0), 0); if (!ult) return; setVistos((v) => { if ((v[room] || 0) >= ult) return v; const nv = Object.assign({}, v, { [room]: ult }); try { localStorage.setItem(chaveVistos(), JSON.stringify(nv)); } catch (e) {} return nv; }); }, [room, msgs]); // Rola pro fim quando chegam mensagens / troca de sala useEffect(() => { if (fimRef.current) fimRef.current.scrollIntoView({ block: "end" }); }, [msgs, room]); function dmRoom(aId, bId) { return "dm:" + [aId, bId].sort().join("_"); } function ehParticipanteDaDm(r, uid) { return r.indexOf("dm:") === 0 && r.slice(3).split("_").indexOf(uid) >= 0; } // Quantas mensagens novas (não vistas, não minhas) numa sala function naoLidas(r) { const visto = vistos[r] || 0; const meuId = souMaster ? "master" : viewer.id; return msgs.filter((m) => m.room === r && (m.ts || 0) > visto && m.fromId !== meuId).length; } // Lista de conversas conforme o papel const conversas = useMemo(() => { const lista = [{ room: "geral", tipo: "geral", titulo: "Geral", sub: "Toda a equipe", icon: "users" }]; if (souMaster) { if (lider) lista.push({ room: "master", tipo: "master", titulo: "Líder · " + lider.nome, sub: "Canal direto com o líder", icon: "message" }); // Monitorar CADA membro da equipe (vê as conversas individuais dele). participantes.forEach((u) => { lista.push({ room: "monitor:" + u.id, tipo: "monitor", titulo: u.nome, sub: "Monitorar · " + rotulo(u.cargo), icon: "eye", user: u }); }); } else { if (souLider) lista.push({ room: "master", tipo: "master", titulo: "Master (KeS)", sub: "Falar com o master", icon: "message" }); participantes.filter((u) => u.id !== viewer.id).forEach((u) => { lista.push({ room: dmRoom(viewer.id, u.id), tipo: "dm", titulo: u.nome, sub: rotulo(u.cargo), user: u, icon: "user" }); }); } return lista; }, [msgs, state.users, souMaster, souLider, viewer.id]); const convAtiva = conversas.find((c) => c.room === room) || conversas[0]; // Mensagens da sala ativa. "monitor:" = todas as conversas individuais do membro. const msgsSala = (() => { if (room.indexOf("monitor:") === 0) { const uid = room.slice(8); return msgs.filter((m) => m.room && m.room.indexOf("dm:") === 0 && m.room.slice(3).split("_").indexOf(uid) >= 0) .sort((a, b) => (a.ts || 0) - (b.ts || 0)); } return msgs.filter((m) => m.room === room); })(); // Pode escrever na sala ativa? const podeEscrever = (() => { if (souMaster) return room === "master"; // master só escreve no canal do líder if (room === "geral") return true; if (room === "master") return souLider; if (room.indexOf("dm:") === 0) return ehParticipanteDaDm(room, viewer.id); return false; })(); function abrir(r) { setRoom(r); setThreadAberta(true); } function enviar() { const t = texto.trim(); if (!t || !podeEscrever || !teamId || !DB || !DB.chatSend) return; const ehGeral = room === "geral"; // 'membros' = e-mails que podem ver a mensagem (o banco usa isso p/ privacidade). let membros = []; if (room.indexOf("dm:") === 0) { membros = room.slice(3).split("_").map((id) => { const u = state.users.find((x) => x.id === id); return String((u && u.email) || "").toLowerCase(); }).filter(Boolean); } else if (room === "master") { membros = [String((lider && lider.email) || "").toLowerCase(), MASTER_EMAIL].filter(Boolean); } const msg = { room: room, geral: ehGeral, membros: ehGeral ? [] : membros, fromId: souMaster ? "master" : viewer.id, fromEmail: meuEmail || MASTER_EMAIL, fromNome: souMaster ? "Master (KeS)" : viewer.nome, fromCargo: souMaster ? "master" : (viewer.cargo || ""), texto: t.slice(0, 4000), ts: Date.now(), }; setTexto(""); DB.chatSend(teamId, msg); } function onKey(e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); enviar(); } } function autorDe(m) { if (m.fromId === "master") return { nome: "Master (KeS)", cor: "var(--warn)" }; const u = state.users.find((x) => x.id === m.fromId); return { nome: m.fromNome || (u ? u.nome : "—"), cor: (u && u.cor) || "var(--kes-red-400)", user: u }; } const meuId = souMaster ? "master" : viewer.id; // Sem equipe selecionada if (!teamId) { return (

Chat

Comunicação interna da equipe.

Nenhuma equipe selecionada.
); } return (

Chat

{souMaster ? "Acompanhe as conversas da equipe e fale com o líder." : "Converse com a sua equipe."}

{souMaster && state.teams && state.teams.length > 1 && (
)}
{/* Lista de conversas */} {/* Conversa ativa */}
{convAtiva ? convAtiva.titulo : "Geral"}
{convAtiva ? convAtiva.sub : ""}
{msgsSala.length === 0 && (
{convAtiva && convAtiva.tipo === "monitor" ? "Sem mensagens nesta conversa." : podeEscrever ? "Comece a conversa 👇" : "Sem mensagens ainda."}
)} {msgsSala.map((m, i) => { const mine = m.fromId === meuId; const aut = autorDe(m); const showNome = (convAtiva && (convAtiva.tipo === "geral" || convAtiva.tipo === "monitor")) && !mine; return (
{showNome &&
{aut.nome}
}
{m.texto}
{horaMsg(m.ts)}
); })}
{podeEscrever ? (