const { Card, DataTable, Button, Icon, IconButton, SearchField, Select, Input, Modal, Badge, Tag, Avatar, EmptyState, Switch, Checkbox, StatCard, SectorTag, Tabs } = window.ACRFlowDesignSystem_ba1e9c; const EMPTY_MEMBER = { name: "", role: "", sector: "gestao-condominial", profile: "operacional", auth: "local", username: "", email: "", phone: "", companies: "all", active: true, password: "", password2: "", mustChange: true }; const GOOGLE_DOMAINS = ["acrcondominio.com.br", "acrcondo.com"]; const domainOk = (email) => GOOGLE_DOMAINS.some((d) => (email || "").toLowerCase().endsWith("@" + d)); function AuthBadge({ auth, size }) { const google = auth === "google"; return ( {google ? : } {google ? "Google OAuth" : "Conta local"} ); } const toUser = (name) => name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").trim().split(/\s+/).filter(Boolean).slice(0, 2).join("."); const RULES = [ { id: "len", label: "Mínimo de 8 caracteres", test: (v) => v.length >= 8 }, { id: "upper", label: "Uma letra maiúscula", test: (v) => /[A-ZÀ-Ü]/.test(v) }, { id: "lower", label: "Uma letra minúscula", test: (v) => /[a-zà-ü]/.test(v) }, { id: "num", label: "Um número", test: (v) => /[0-9]/.test(v) }, { id: "special", label: "Um caractere especial (!@#$%&*)", test: (v) => /[^A-Za-z0-9À-ü]/.test(v) }, ]; const passwordOk = (v) => RULES.every((r) => r.test(v || "")); const generatePassword = () => { const up = "ABCDEFGHJKLMNPQRSTUVWXYZ", low = "abcdefghijkmnpqrstuvwxyz", num = "23456789", sp = "!@#$%&*?"; const pick = (s) => s[Math.floor(Math.random() * s.length)]; const base = [pick(up), pick(low), pick(num), pick(sp)]; const pool = up + low + num + sp; while (base.length < 12) base.push(pick(pool)); return base.sort(() => Math.random() - 0.5).join(""); }; function PasswordFields({ value, onChange, editing, onResetChange }) { const [show, setShow] = React.useState(false); const [reset, setReset] = React.useState(!editing); const pwd = value.password || ""; const match = pwd && pwd === (value.password2 || ""); const openReset = () => { setReset(true); onResetChange(true); }; if (editing && !reset) { return (
Senha atual mantida
); } return (
onChange({ ...value, password: e.target.value })} /> onChange({ ...value, password2: e.target.value })} />
{RULES.map((r) => { const ok = r.test(pwd); return ( {r.label} ); })}
onChange({ ...value, mustChange: !value.mustChange })} label="Exigir troca no primeiro acesso" description="O colaborador define a própria senha ao entrar." />
); } function CompanyScope({ value, companies, onChange }) { const all = value === "all"; const ids = all ? [] : value; return (
Empresas que pode gerir
{!all ? (
{companies.map((c) => ( ))}
) : null}
); } function TeamScreen({ onToast }) { const D = window.useStore(); const S = window.ACRStore; const rows = D.team || []; const [salvando, setSalvando] = React.useState(false); const falhou = (r) => { if (!r.ok) { onToast({ tone: "danger", title: "Não foi possível gravar", message: r.error }); return true; } return false; }; const [q, setQ] = React.useState(""); const [profile, setProfile] = React.useState("todos"); const [form, setForm] = React.useState(null); const [remove, setRemove] = React.useState(null); const [pwdReset, setPwdReset] = React.useState(false); const companies = D.companies; const sectors = D.sectorsCfg; const list = rows.filter((r) => (profile === "todos" || r.profile === profile) && (r.name.toLowerCase().includes(q.toLowerCase()) || (r.email || "").toLowerCase().includes(q.toLowerCase()) || (r.username || "").includes(q.toLowerCase()))); const scopeLabel = (m) => (m.companies === "all" ? "Todas as empresas" : m.companies.length + (m.companies.length === 1 ? " empresa" : " empresas")); const needsPassword = form ? (form.auth !== "google" && (!form.id || pwdReset)) : false; const pwdValid = form ? (!needsPassword || (passwordOk(form.password) && form.password === form.password2)) : true; const userValid = form ? (form.auth === "google" ? domainOk(form.email) : !!(form.username || "").trim() && !rows.some((r) => r.username === form.username && r.id !== form.id)) : true; const save = async () => { const { password2, ...rest } = form; if (!form.name || !form.name.trim()) { onToast({ tone: "danger", title: "Informe o nome", message: "O nome completo é obrigatório." }); return; } if (!form.email || !form.email.trim()) { onToast({ tone: "danger", title: "Informe o e-mail", message: "O e-mail é obrigatório para o acesso." }); return; } setSalvando(true); const r = await S.salvarUsuario(rest); setSalvando(false); if (falhou(r)) return; onToast({ tone: "success", title: form.id ? "Colaborador atualizado" : "Colaborador cadastrado", message: form.name + " · " + (form.profile === "supervisor" ? "Supervisor" : "Operacional") }); setForm(null); setPwdReset(false); }; return (

Controle de Acesso

Colaboradores, forma de autenticação, perfil de acesso e quais empresas cada um está apto a gerir.
r.active).length} tone="brand" icon={} /> r.profile === "supervisor").length} icon={} /> r.profile === "operacional").length} icon={} /> r.auth === "google").length} icon={} /> r.auth !== "google").length} icon={} />
r.profile === "supervisor").length }, { id: "operacional", label: "Operacionais", count: rows.filter((r) => r.profile === "operacional").length }, ]} /> setQ(e.target.value)} />}> {list.length ? ( ( {r.name} {r.auth === "google" ? r.email : (r.username ? r.username + " · " : "") + r.email} ) }, { key: "auth", label: "Autenticação", width: 150, render: (r) => }, { key: "profile", label: "Perfil", width: 140, render: (r) => {r.profile === "supervisor" ? "Supervisor" : "Operacional"} }, { key: "sector", label: "Setor", width: 190, render: (r) => s.id === r.sector) || {}).name} size="sm" /> }, { key: "companies", label: "Empresas", width: 210, render: (r) => ( r.companies === "all" ? Todas as empresas : {r.companies.slice(0, 2).map((id) => {(companies.find((c) => c.id === id) || {}).name})}{r.companies.length > 2 ? +{r.companies.length - 2} : null}) }, { key: "load", label: "Demandas", width: 100, render: (r) => {r.load} }, { key: "active", label: "Status", width: 96, render: (r) => {r.active ? "Ativo" : "Inativo"} }, { key: "acoes", label: "", width: 92, render: (r) => ( } onClick={() => { setPwdReset(false); setForm({ ...r, password: "", password2: "" }); }} /> } onClick={() => setRemove(r)} /> ) }, ]} /> ) : ( } title="Nenhum colaborador encontrado." description="Ajuste a busca ou cadastre um novo colaborador." action={} /> )} {form ? ( { setForm(null); setPwdReset(false); }} footer={<>}>
Forma de autenticação
{[["google", "Google OAuth", "Entra com a conta Google do domínio autorizado — sem senha no FlowTix"], ["local", "Conta local", "Login e senha gerenciados aqui no FlowTix"]].map(([k, l, d]) => ( ))}
setForm({ ...form, name: e.target.value, username: form.id || form.userTouched ? form.username : toUser(e.target.value) })} /> {form.auth === "google" ? ( } hint={"Domínios liberados: " + GOOGLE_DOMAINS.map((d) => "@" + d).join(", ") + ". O vínculo é feito só pelo e-mail — sem senha local."} error={form.email && !domainOk(form.email) ? "Domínio não autorizado para login Google." : undefined} onChange={(e) => setForm({ ...form, email: e.target.value })} /> ) : (
} hint="Usado para entrar no FlowTix — sem espaços e único." error={form.username && rows.some((r) => r.username === form.username && r.id !== form.id) ? "Este login já está em uso." : undefined} onChange={(e) => setForm({ ...form, username: e.target.value.toLowerCase().replace(/\s+/g, "."), userTouched: true })} /> setForm({ ...form, email: e.target.value })} />
)}
setForm({ ...form, phone: e.target.value })} /> setForm({ ...form, role: e.target.value })} />