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
Redefinir senha
);
}
return (
);
}
function CompanyScope({ value, companies, onChange }) {
const all = value === "all";
const ids = all ? [] : value;
return (
Empresas que pode gerir
onChange("all")}
style={{ flex: 1, padding: "9px 12px", textAlign: "left", cursor: "pointer", borderRadius: "var(--radius-sm)", border: "1px solid " + (all ? "var(--accent-primary)" : "var(--border-default)"), background: all ? "var(--surface-sunken)" : "var(--surface-card)" }}>
Todas as empresas
Inclui novos cadastros automaticamente
onChange(ids.length ? ids : [companies[0].id])}
style={{ flex: 1, padding: "9px 12px", textAlign: "left", cursor: "pointer", borderRadius: "var(--radius-sm)", border: "1px solid " + (!all ? "var(--accent-primary)" : "var(--border-default)"), background: !all ? "var(--surface-sunken)" : "var(--surface-card)" }}>
Selecionar empresas
Acesso restrito às marcadas
{!all ? (
{companies.map((c) => (
onChange(ids.includes(c.id) ? ids.filter((i) => i !== c.id) : [...ids, c.id])} />
{c.name}
{c.purpose} · {c.doc}
{!c.active ? Inativa : null}
))}
) : 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.
} onClick={() => setForm({ ...EMPTY_MEMBER })}>Novo colaborador
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={ setForm({ ...EMPTY_MEMBER })}>Novo colaborador } />
)}
{form ? (
{ setForm(null); setPwdReset(false); }}
footer={<> { setForm(null); setPwdReset(false); }}>Cancelar {form.id ? "Salvar alterações" : "Cadastrar colaborador"} >}>
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, auth: k })}
style={{ flex: 1, padding: "9px 12px", textAlign: "left", cursor: "pointer", borderRadius: "var(--radius-sm)", border: "1px solid " + (form.auth === k ? "var(--accent-primary)" : "var(--border-default)"), background: form.auth === k ? "var(--surface-sunken)" : "var(--surface-card)" }}>
{k === "google" ? : }{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 })} />
({ value: s.id, label: s.name }))} onChange={(e) => setForm({ ...form, sector: e.target.value })} />
Perfil de acesso
{[["supervisor", "Supervisor", "Aprova encerramentos, vê tudo do setor"], ["operacional", "Operacional", "Executa demandas e solicita encerramento"]].map(([k, l, d]) => (
setForm({ ...form, profile: k })}
style={{ flex: 1, padding: "9px 12px", textAlign: "left", cursor: "pointer", borderRadius: "var(--radius-sm)", border: "1px solid " + (form.profile === k ? "var(--accent-primary)" : "var(--border-default)"), background: form.profile === k ? "var(--surface-sunken)" : "var(--surface-card)" }}>
{l}
{d}
))}
setForm({ ...form, companies: v })} />
{form.auth === "google" ? (
Autenticação via Google OAuth — o FlowTix não guarda senha desta conta. O perfil ({form.profile === "supervisor" ? "Supervisor" : "Operacional"}) e o escopo de empresas continuam definidos aqui.
) : (
Senha de acesso · conta local
)}
setForm({ ...form, active: !form.active })} label="Colaborador ativo" description="Inativos não recebem novas atribuições nem notificações." />
) : null}
{remove ? (
setRemove(null)}
footer={<> setRemove(null)}>Cancelar
0 || salvando} onClick={async () => { setSalvando(true); const rr = await S.removerUsuario(remove.id); setSalvando(false); if (falhou(rr)) return; onToast({ tone: "danger", title: "Colaborador removido", message: remove.name }); setRemove(null); }}>Remover >}>
{remove.load > 0
? {remove.name} possui {remove.load} demandas ativas . Reatribua as demandas antes de remover — ou desative o cadastro.
: O acesso é revogado imediatamente. O histórico das demandas em que participou é preservado. }
) : null}
);
}
Object.assign(window, { TeamScreen });