feat(condominios): adicionar opcoes de editar e excluir condominios e funcionarios com icones

This commit is contained in:
Tiago
2026-10-13 15:55:36 -03:00
parent 5854aa450b
commit 22e0171e7d
2 changed files with 85 additions and 7 deletions
@@ -72,7 +72,7 @@ public final class ClienteHandlers implements HttpHandler {
if(p.length==4&&"condominios".equals(p[0])&&"avisos".equals(p[2])){ long cond=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,cond)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} if("GET".equals(method)) detalheAviso(ex,c,cond,id(p[3])); else if("DELETE".equals(method)) excluirAviso(ex,c,empresaId,cond,id(p[3]),funcionarioId); else HttpResponses.methodNotAllowed(ex); return; }
if(p.length==5&&"condominios".equals(p[0])&&"avisos".equals(p[2])&&"reenviar".equals(p[4])&&"POST".equals(method)){ long cond=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,cond)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} reenviarAviso(ex,c,empresaId,cond,id(p[3])); return; }
if(p.length==1&&"condominios".equals(p[0])){ if("POST".equals(method)) criarCondominio(ex,c,funcionarioId,cargo); else if("GET".equals(method)) listarCondominios(ex,c,funcionarioId,cargo); else HttpResponses.methodNotAllowed(ex); return; }
if(p.length==2&&"condominios".equals(p[0])){ long id=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,id)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} if("GET".equals(method)) detalheCondominio(ex,c,id); else if("PUT".equals(method)) atualizarCondominio(ex,c,id,funcionarioId); else HttpResponses.methodNotAllowed(ex); return; }
if(p.length==2&&"condominios".equals(p[0])){ long id=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,id)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} if("GET".equals(method)) detalheCondominio(ex,c,id); else if("PUT".equals(method)) atualizarCondominio(ex,c,id,funcionarioId); else if("DELETE".equals(method)) excluirCondominio(ex,c,empresaId,id,funcionarioId,cargo); else HttpResponses.methodNotAllowed(ex); return; }
if(p.length==3&&"condominios".equals(p[0])&&"respostas".equals(p[2])){ long cond=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,cond)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} if("GET".equals(method)) listarRespostas(ex,c,cond); else if("POST".equals(method)) registrarResposta(ex,c,funcionarioId,cond); else HttpResponses.methodNotAllowed(ex); return; }
if(p.length==1&&"contatos".equals(p[0])&&"POST".equals(method)){ criarContato(ex,c,funcionarioId); return; }
if(p.length==3&&"condominios".equals(p[0])&&"contatos".equals(p[2])&&"GET".equals(method)){ long cond=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,cond)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} listarContatos(ex,c,cond); return; }
@@ -96,7 +96,8 @@ public final class ClienteHandlers implements HttpHandler {
private void listarCondominios(HttpExchange ex,Connection c,long fid,String cargo)throws Exception{if(adminEmpresa(cargo)){listar(ex,c,"SELECT * FROM condominios ORDER BY nome","condominios");return;}HttpResponses.json(ex,200,Map.of("condominios",rows(c,"SELECT c.* FROM condominios c JOIN funcionario_condominio fc ON fc.condominio_id=c.id WHERE fc.funcionario_id=? ORDER BY c.nome",Arrays.asList(fid))));}
private boolean podeAcessarCondominio(Connection c,String cargo,long fid,long cond)throws SQLException{return adminEmpresa(cargo)||count(c,"SELECT count(*) FROM funcionario_condominio WHERE funcionario_id=? AND condominio_id=?",fid,cond)>0;}
private void detalheCondominio(HttpExchange ex,Connection c,long id)throws Exception{Map<String,Object> cond=one(c,"SELECT * FROM condominios WHERE id=?",Arrays.asList(id)); if(cond==null){HttpResponses.json(ex,404,Map.of("erro","condominio_nao_encontrado"));return;} Map<String,Object> sind=one(c,"SELECT cc.*, c.nome contato_nome,c.telefone contato_telefone,c.email contato_email FROM contato_condominio cc JOIN contatos c ON c.id=cc.contato_id WHERE cc.condominio_id=? AND cc.tipo='sindico' AND cc.status='ativo' ORDER BY cc.data_inicio_mandato DESC NULLS LAST LIMIT 1",Arrays.asList(id)); long ass=count(c,"SELECT count(*) FROM assembleias WHERE condominio_id=? AND data_hora>now() AND status='agendada'",id); long av=count(c,"SELECT count(*) FROM avisos WHERE condominio_id=? AND (data_expiracao IS NULL OR data_expiracao>now())",id); Map<String,Object> resp=new LinkedHashMap<>();resp.put("condominio",cond);resp.put("sindico_atual",sind);resp.put("assembleias_futuras",ass);resp.put("avisos_ativos",av);HttpResponses.json(ex,200,resp);}
private void atualizarCondominio(HttpExchange ex,Connection c,long id,long fid)throws Exception{Map<String,String>b=body(ex); Map<String,Object> old=one(c,"SELECT * FROM condominios WHERE id=?",Arrays.asList(id)); if(old==null){HttpResponses.json(ex,404,Map.of("erro","condominio_nao_encontrado"));return;} try(PreparedStatement ps=c.prepareStatement("UPDATE condominios SET nome=COALESCE(?,nome), cnpj=COALESCE(?,cnpj), endereco=COALESCE(?,endereco) WHERE id=?")){ps.setString(1,b.get("nome"));ps.setString(2,b.get("cnpj"));ps.setString(3,b.get("endereco"));ps.setLong(4,id);ps.executeUpdate();} audit(c,fid,"update","condominios",id,old,b); detalheCondominio(ex,c,id);}
private void atualizarCondominio(HttpExchange ex,Connection c,long id,long fid)throws Exception{Map<String,String>b=body(ex); Map<String,Object> old=one(c,"SELECT * FROM condominios WHERE id=?",Arrays.asList(id)); if(old==null){HttpResponses.json(ex,404,Map.of("erro","condominio_nao_encontrado"));return;} String cnpjVal=null; if(b.get("cnpj")!=null&&!b.get("cnpj").isBlank()){cnpjVal=cnpj(b.get("cnpj")); if(one(c,"SELECT id FROM condominios WHERE regexp_replace(cnpj,'\\D','','g')=? AND id<>?",Arrays.asList(cnpjVal,id))!=null){HttpResponses.json(ex,400,Map.of("erro","cnpj_duplicado","mensagem","Já existe outro condomínio cadastrado com este CNPJ."));return;}} try(PreparedStatement ps=c.prepareStatement("UPDATE condominios SET nome=COALESCE(?,nome), cnpj=COALESCE(?,cnpj), endereco=COALESCE(?,endereco) WHERE id=?")){ps.setString(1,b.get("nome")!=null&&!b.get("nome").isBlank()?b.get("nome"):null);ps.setString(2,cnpjVal);ps.setString(3,b.get("endereco"));ps.setLong(4,id);ps.executeUpdate();} audit(c,fid,"update","condominios",id,old,b); detalheCondominio(ex,c,id);}
private void excluirCondominio(HttpExchange ex,Connection c,long emp,long cond,long fid,String cargo)throws Exception{if(!adminEmpresa(cargo)){HttpResponses.json(ex,403,Map.of("erro","acesso_restrito","mensagem","Apenas administradores podem excluir condomínios."));return;} Map<String,Object> old=one(c,"SELECT * FROM condominios WHERE id=?",Arrays.asList(cond)); if(old==null){HttpResponses.json(ex,404,Map.of("erro","condominio_nao_encontrado"));return;} c.setAutoCommit(false); try{try(PreparedStatement ps=c.prepareStatement("DELETE FROM respostas WHERE condominio_id=?")){ps.setLong(1,cond);ps.executeUpdate();} List<Map<String,Object>> avisos=rows(c,"SELECT id FROM avisos WHERE condominio_id=?",Arrays.asList(cond)); for(Map<String,Object> av:avisos){long aid=((Number)av.get("id")).longValue(); try(PreparedStatement ps=c.prepareStatement("DELETE FROM aviso_documentos WHERE aviso_id=?")){ps.setLong(1,aid);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM aviso_destinatarios WHERE aviso_id=?")){ps.setLong(1,aid);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM fila_envio WHERE tipo='aviso' AND referencia_id=?")){ps.setLong(1,aid);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM avisos WHERE id=?")){ps.setLong(1,aid);ps.executeUpdate();}} try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("DELETE FROM logs_envio_avisos WHERE empresa_id=? AND condominio_id=?")){ps.setLong(1,emp);ps.setLong(2,cond);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM assembleias WHERE condominio_id=?")){ps.setLong(1,cond);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM espacos_comuns WHERE condominio_id=?")){ps.setLong(1,cond);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM documentos WHERE condominio_id=?")){ps.setLong(1,cond);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM contato_condominio WHERE condominio_id=?")){ps.setLong(1,cond);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM funcionario_condominio WHERE condominio_id=?")){ps.setLong(1,cond);ps.executeUpdate();} try(PreparedStatement ps=c.prepareStatement("DELETE FROM condominios WHERE id=?")){ps.setLong(1,cond);ps.executeUpdate();} audit(c,fid,"delete","condominios",cond,old,Map.of("ok",true)); c.commit(); HttpResponses.json(ex,200,Map.of("ok",true));}catch(Exception err){c.rollback();throw err;}finally{c.setAutoCommit(true);}}
private void listarRespostas(HttpExchange ex,Connection c,long cond)throws Exception{HttpResponses.json(ex,200,Map.of("respostas",rows(c,"SELECT r.*, co.nome contato_nome, co.telefone contato_telefone, co.email contato_email FROM respostas r LEFT JOIN contatos co ON co.id=r.contato_id WHERE r.condominio_id=? ORDER BY r.recebido_em DESC,id DESC",Arrays.asList(cond))));}
private void registrarResposta(HttpExchange ex,Connection c,long fid,Long condPath)throws Exception{String raw=rawBody(ex);Map<String,String>b=SimpleJson.parseObject(raw);Long contato=optLong(firstNonBlank(b.get("contato_id"),b.get("destinatario_id")));Long cond=condPath!=null?condPath:optLong(b.get("condominio_id"));if(cond==null&&contato!=null){Map<String,Object> link=one(c,"SELECT condominio_id FROM contato_condominio WHERE contato_id=? AND status='ativo' ORDER BY CASE WHEN tipo='sindico' THEN 0 ELSE 1 END, condominio_id LIMIT 1",Arrays.asList(contato));if(link!=null)cond=((Number)link.get("condominio_id")).longValue();}if(cond==null){HttpResponses.json(ex,400,Map.of("erro","condominio_obrigatorio","mensagem","Informe condominio_id ou contato_id vinculado a um condomínio."));return;}Map<String,Object> contatoRow=contato==null?null:one(c,"SELECT id,nome,telefone,email FROM contatos WHERE id=?",Arrays.asList(contato));String texto=firstNonBlank(b.get("resposta"),b.get("resposta_texto"),b.get("mensagem"),b.get("texto"),b.get("body"));if(texto.isBlank()){HttpResponses.json(ex,400,Map.of("erro","resposta_obrigatoria","mensagem","Informe a resposta recebida."));return;}long rid=insert(c,"INSERT INTO respostas (condominio_id,contato_id,mensagem_id,tipo_origem,nome_origem,telefone_origem,resposta_texto,mensagem_original,payload,origem,status) VALUES (?,?,?,?,?,?,?,?,?,?,?)",Arrays.asList(cond,contato,optLong(b.get("mensagem_id")),nullable(firstNonBlank(b.get("tipo"),b.get("tipo_origem"))),nullable(firstNonBlank(b.get("nome"),contatoRow==null?null:str(contatoRow.get("nome")))),nullable(firstNonBlank(b.get("telefone"),contatoRow==null?null:str(contatoRow.get("telefone")))),texto,nullable(firstNonBlank(b.get("mensagem_original"),b.get("pergunta"),b.get("mensagem_enviada"))),raw,firstNonBlank(b.get("origem"),"n8n"),firstNonBlank(b.get("status"),"recebida")));audit(c,fid,"create","respostas",rid,null,b);HttpResponses.json(ex,201,Map.of("ok",true,"resposta",one(c,"SELECT * FROM respostas WHERE id=?",Arrays.asList(rid)))) ;}
private void criarContato(HttpExchange ex,Connection c,long fid)throws Exception{Map<String,String>b=body(ex); long id=insert(c,"INSERT INTO contatos (nome,telefone,email) VALUES (?,?,?)",Arrays.asList(req(b,"nome"),nullable(b.get("telefone")),nullable(b.get("email")))); audit(c,fid,"create","contatos",id,null,b); HttpResponses.json(ex,201,Map.of("contato",one(c,"SELECT * FROM contatos WHERE id=?",Arrays.asList(id))));}
+82 -5
View File
@@ -17,7 +17,7 @@ const Icons = {
function renderCondominios(){
const q=termo('#cond-search-input');
const lista=condominios.filter(c=>contem(c,q,['id','nome','cnpj','endereco']));
rows.innerHTML=lista.map(c=>`<tr><td>${c.id}</td><td>${esc(c.nome)}</td><td>${esc(c.cnpj||'')}</td><td>${esc(c.endereco||'')}</td><td style="text-align:right"><div class="table-actions"><a class="table-icon-btn" href="/cliente/condominio-detalhe.html?id=${c.id}" title="Abrir condomínio" aria-label="Abrir condomínio">${Icons.eye}</a></div></td></tr>`).join('')||'<tr><td colspan="5">Nenhum condomínio encontrado.</td></tr>';
rows.innerHTML=lista.map(c=>`<tr><td>${c.id}</td><td>${esc(c.nome)}</td><td>${esc(c.cnpj||'')}</td><td>${esc(c.endereco||'')}</td><td style="text-align:right"><div class="table-actions"><a class="table-icon-btn" href="/cliente/condominio-detalhe.html?id=${c.id}" title="Abrir condomínio" aria-label="Abrir condomínio">${Icons.eye}</a><button class="table-icon-btn" type="button" onclick="abrirEditarCondominio(${c.id})" title="Editar condomínio" aria-label="Editar condomínio">${Icons.edit}</button><button class="table-icon-btn danger" type="button" onclick="abrirExcluirCondominio(${c.id})" title="Excluir condomínio" aria-label="Excluir condomínio">${Icons.trash}</button></div></td></tr>`).join('')||'<tr><td colspan="5">Nenhum condomínio encontrado.</td></tr>';
}
async function load(){
@@ -28,6 +28,8 @@ async function load(){
}catch(e){Util.toast(e.message,true)}
}
function condominioPorId(id){return condominios.find(x=>Number(x.id)===Number(id))}
window.abrirNovoCondominio=()=>{
abrirModal('Novo condomínio','<form id="cond-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required></div><div class="field"><label>CNPJ</label><input name="cnpj" required></div><div class="field"><label>Endereço</label><input name="endereco"></div>'+formActions('Criar')+'</form>');
document.querySelector('#cond-form').onsubmit=async e=>{
@@ -42,6 +44,38 @@ window.abrirNovoCondominio=()=>{
};
};
window.abrirEditarCondominio=id=>{
const c=condominioPorId(id);
if(!c)return;
abrirModal('Editar condomínio','<form id="cond-edit-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required value="'+esc(c.nome||'')+'"></div><div class="field"><label>CNPJ</label><input name="cnpj" required value="'+esc(c.cnpj||'')+'"></div><div class="field"><label>Endereço</label><input name="endereco" value="'+esc(c.endereco||'')+'"></div>'+formActions('Salvar alterações')+'</form>');
document.querySelector('#cond-edit-form').onsubmit=async e=>{
e.preventDefault();
try{
await Api.put('/api/condominios/'+id,Util.formData(e.target));
Util.toast('Condomínio atualizado com sucesso');
fecharModalCondominios();
await load();
if(Auth.isAdminEmpresa())await loadFuncionarios();
}catch(err){Util.toast(err.message,true)}
};
};
window.abrirExcluirCondominio=async id=>{
const c=condominioPorId(id);
if(!c)return;
if(!Auth.isAdminEmpresa()){
Util.toast('Apenas o administrador pode excluir condomínios.',true);
return;
}
if(!await Util.confirm('Deseja realmente excluir o condomínio "'+esc(c.nome||'')+'"? Todos os avisos, assembleias, documentos e vínculos deste condomínio serão permanentemente removidos.','Excluir condomínio'))return;
try{
await Api.request('/api/condominios/'+id,{method:'DELETE'});
Util.toast('Condomínio excluído com sucesso');
await load();
if(Auth.isAdminEmpresa())await loadFuncionarios();
}catch(err){Util.toast(err.message,true)}
};
async function linksPorFuncionario(){
const mapa=new Map();
await Promise.all(condominios.map(async c=>{
@@ -64,7 +98,7 @@ function renderFuncionarios(){
const lista=funcionarios.filter(f=>contem(f,q,['nome','email','telefone','cargo','status']));
funcRows.innerHTML=lista.map(f=>{
const acao=f.cargo==='admin_empresa'?'<span class="muted">Acesso total</span>':`<div class="table-actions start"><button class="table-icon-btn" type="button" onclick="abrirModalCondominios(${f.id})" title="Vincular condomínios" aria-label="Vincular condomínios">${Icons.building}</button></div>`;
const opcoes=f.cargo==='admin_empresa'?'<span class="muted">-</span>':`<div class="table-actions start"><button class="table-icon-btn" type="button" onclick="abrirEditarFuncionario(${f.id})" title="Editar funcionário" aria-label="Editar funcionário">${Icons.edit}</button><button class="table-icon-btn danger" type="button" onclick="abrirExcluirFuncionario(${f.id})" title="Excluir funcionário" aria-label="Excluir funcionário">${Icons.trash}</button></div>`;
const opcoes=`<div class="table-actions start"><button class="table-icon-btn" type="button" onclick="abrirEditarFuncionario(${f.id})" title="Editar funcionário" aria-label="Editar funcionário">${Icons.edit}</button><button class="table-icon-btn danger" type="button" onclick="abrirExcluirFuncionario(${f.id})" title="Excluir funcionário" aria-label="Excluir funcionário">${Icons.trash}</button></div>`;
return '<tr><td>'+esc(f.nome)+'</td><td>'+esc(f.email)+'</td><td>'+esc(f.telefone||'')+'</td><td>'+esc(f.cargo||'')+'</td><td>'+Util.badge(f.status)+'</td><td>'+acao+'</td><td>'+opcoes+'</td></tr>';
}).join('')||'<tr><td colspan="7">Nenhum funcionário encontrado.</td></tr>';
}
@@ -95,10 +129,53 @@ window.abrirNovoFuncionario=()=>{
};
function funcionarioPorId(funcId){return funcionarios.find(x=>Number(x.id)===Number(funcId))}
window.abrirEditarFuncionario=funcId=>{const f=funcionarioPorId(funcId);if(!f)return;abrirModal('Editar funcionário','<form id="func-edit-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required value="'+esc(f.nome||'')+'"></div><div class="field"><label>E-mail de login</label><input name="email" type="email" required value="'+esc(f.email||'')+'"></div><div class="field"><label>WhatsApp</label><input name="telefone" value="'+esc(f.telefone||'')+'"></div><div class="field"><label>Status</label><select name="status"><option value="ativo" '+(f.status==='ativo'?'selected':'')+'>Ativo</option><option value="inativo" '+(f.status==='inativo'?'selected':'')+'>Inativo</option></select></div><div class="field"><label>Nova senha</label><input name="senha" type="password"></div>'+formActions('Salvar')+'</form>');document.querySelector('#func-edit-form').onsubmit=async e=>{e.preventDefault();try{await Api.put('/api/funcionarios/'+funcId,Util.formData(e.target));Util.toast('Funcionário atualizado');fecharModalCondominios();loadFuncionarios()}catch(err){Util.toast(err.message,true)}}};
window.abrirExcluirFuncionario=async funcId=>{const f=funcionarioPorId(funcId);if(!f)return;if(!await Util.confirm('Deseja realmente excluir o acesso de "'+esc(f.nome||'')+'"? O funcionário será inativado e perderá o acesso ao sistema.','Excluir funcionário'))return;try{await Api.request('/api/funcionarios/'+funcId,{method:'DELETE'});Util.toast('Funcionário inativado');loadFuncionarios()}catch(err){Util.toast(err.message,true)}};
function renderModalCondominios(){const f=funcionarios.find(x=>Number(x.id)===Number(funcAtual));if(!f)return;const vinculados=funcLinks.get(Number(funcAtual))||[];const vinculadosIds=new Set(vinculados.map(c=>Number(c.id)));const disponiveis=condominios.filter(c=>!vinculadosIds.has(Number(c.id)));modalTitle.textContent=`Condomínios de ${f.nome}`;const listaVinculados=vinculados.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn danger" type="button" onclick="desvincularFuncionarioCondominio(${funcAtual},${c.id})">Desvincular</button></div>`).join('')||'<p class="muted">Nenhum condomínio vinculado.</p>';const listaDisponiveis=disponiveis.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn secondary" type="button" onclick="vincularFuncionarioCondominio(${funcAtual},${c.id})">Vincular</button></div>`).join('')||'<p class="muted">Todos os condomínios já estão vinculados.</p>';modalBody.innerHTML=`<div class="modal-grid"><section><h3>Vinculados</h3>${listaVinculados}</section><section><h3>Não vinculados</h3>${listaDisponiveis}</section></div><div class="modal-actions" style="margin-top: 18px;"><button class="btn secondary" type="button" onclick="fecharModalCondominios()">Fechar</button></div>`}
window.abrirEditarFuncionario=funcId=>{
const f=funcionarioPorId(funcId);
if(!f)return;
const isAdmin=f.cargo==='admin_empresa';
abrirModal(
isAdmin?'Editar administrador':'Editar funcionário',
'<form id="func-edit-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required value="'+esc(f.nome||'')+'"></div><div class="field"><label>E-mail de login</label><input name="email" type="email" required value="'+esc(f.email||'')+'"></div><div class="field"><label>WhatsApp</label><input name="telefone" value="'+esc(f.telefone||'')+'"></div>'+(isAdmin?'':'<div class="field"><label>Status</label><select name="status"><option value="ativo" '+(f.status==='ativo'?'selected':'')+'>Ativo</option><option value="inativo" '+(f.status==='inativo'?'selected':'')+'>Inativo</option></select></div>')+'<div class="field"><label>Nova senha</label><input name="senha" type="password" placeholder="Deixe em branco para manter a atual"></div>'+formActions('Salvar alterações')+'</form>'
);
document.querySelector('#func-edit-form').onsubmit=async e=>{
e.preventDefault();
try{
await Api.put('/api/funcionarios/'+funcId,Util.formData(e.target));
Util.toast('Cadastro atualizado com sucesso');
fecharModalCondominios();
loadFuncionarios();
}catch(err){Util.toast(err.message,true)}
};
};
window.abrirExcluirFuncionario=async funcId=>{
const f=funcionarioPorId(funcId);
if(!f)return;
if(f.cargo==='admin_empresa'){
Util.toast('A conta principal (admin_empresa) não pode ser excluída.',true);
return;
}
if(!await Util.confirm('Deseja realmente excluir o acesso de "'+esc(f.nome||'')+'"? O funcionário será inativado e perderá o acesso ao sistema.','Excluir funcionário'))return;
try{
await Api.request('/api/funcionarios/'+funcId,{method:'DELETE'});
Util.toast('Funcionário inativado');
loadFuncionarios();
}catch(err){Util.toast(err.message,true)}
};
function renderModalCondominios(){
const f=funcionarios.find(x=>Number(x.id)===Number(funcAtual));
if(!f)return;
const vinculados=funcLinks.get(Number(funcAtual))||[];
const vinculadosIds=new Set(vinculados.map(c=>Number(c.id)));
const disponiveis=condominios.filter(c=>!vinculadosIds.has(Number(c.id)));
modalTitle.textContent=`Condomínios de ${f.nome}`;
const listaVinculados=vinculados.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn danger" type="button" onclick="desvincularFuncionarioCondominio(${funcAtual},${c.id})">Desvincular</button></div>`).join('')||'<p class="muted">Nenhum condomínio vinculado.</p>';
const listaDisponiveis=disponiveis.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn secondary" type="button" onclick="vincularFuncionarioCondominio(${funcAtual},${c.id})">Vincular</button></div>`).join('')||'<p class="muted">Todos os condomínios já estão vinculados.</p>';
modalBody.innerHTML=`<div class="modal-grid"><section><h3>Vinculados</h3>${listaVinculados}</section><section><h3>Não vinculados</h3>${listaDisponiveis}</section></div><div class="modal-actions" style="margin-top: 18px;"><button class="btn secondary" type="button" onclick="fecharModalCondominios()">Fechar</button></div>`;
}
window.abrirModalCondominios=async funcId=>{funcAtual=Number(funcId);try{await refreshLinks();renderModalCondominios();modal.classList.remove('hidden')}catch(err){Util.toast(err.message,true)}};
window.fecharModalCondominios=()=>{modal.classList.add('hidden');funcAtual=null;modalBody.innerHTML=''};
window.vincularFuncionarioCondominio=async (funcId,condId)=>{try{await Api.post(`/api/condominios/${condId}/funcionarios-vinculados`,{funcionario_id:funcId});Util.toast('Funcionário vinculado');await refreshLinks();renderModalCondominios()}catch(err){Util.toast(err.message,true)}};