From 22e0171e7df76408ce68fedad3a1b95d6b2b4019 Mon Sep 17 00:00:00 2001 From: Tiago Date: Tue, 13 Oct 2026 15:55:36 -0300 Subject: [PATCH] feat(condominios): adicionar opcoes de editar e excluir condominios e funcionarios com icones --- .../tenant/http/ClienteHandlers.java | 5 +- src/main/webapp/cliente/condominios.js | 87 +++++++++++++++++-- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java b/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java index adfdbca..483d221 100644 --- a/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java +++ b/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java @@ -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 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 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 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{Mapb=body(ex); Map 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{Mapb=body(ex); Map 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 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> avisos=rows(c,"SELECT id FROM avisos WHERE condominio_id=?",Arrays.asList(cond)); for(Map 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);Mapb=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 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 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{Mapb=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))));} diff --git a/src/main/webapp/cliente/condominios.js b/src/main/webapp/cliente/condominios.js index 042aad5..88341ce 100644 --- a/src/main/webapp/cliente/condominios.js +++ b/src/main/webapp/cliente/condominios.js @@ -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=>`${c.id}${esc(c.nome)}${esc(c.cnpj||'')}${esc(c.endereco||'')}`).join('')||'Nenhum condomínio encontrado.'; + rows.innerHTML=lista.map(c=>`${c.id}${esc(c.nome)}${esc(c.cnpj||'')}${esc(c.endereco||'')}
${Icons.eye}
`).join('')||'Nenhum condomínio encontrado.'; } 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','
'+formActions('Criar')+'
'); 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','
'+formActions('Salvar alterações')+'
'); + 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'?'Acesso total':`
`; - const opcoes=f.cargo==='admin_empresa'?'-':`
`; + const opcoes=`
`; return ''+esc(f.nome)+''+esc(f.email)+''+esc(f.telefone||'')+''+esc(f.cargo||'')+''+Util.badge(f.status)+''+acao+''+opcoes+''; }).join('')||'Nenhum funcionário encontrado.'; } @@ -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','
'+formActions('Salvar')+'
');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=>`
${esc(c.nome)}
`).join('')||'

Nenhum condomínio vinculado.

';const listaDisponiveis=disponiveis.map(c=>`
${esc(c.nome)}
`).join('')||'

Todos os condomínios já estão vinculados.

';modalBody.innerHTML=``} +window.abrirEditarFuncionario=funcId=>{ + const f=funcionarioPorId(funcId); + if(!f)return; + const isAdmin=f.cargo==='admin_empresa'; + abrirModal( + isAdmin?'Editar administrador':'Editar funcionário', + '
'+(isAdmin?'':'
')+'
'+formActions('Salvar alterações')+'
' + ); + 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=>`
${esc(c.nome)}
`).join('')||'

Nenhum condomínio vinculado.

'; + const listaDisponiveis=disponiveis.map(c=>`
${esc(c.nome)}
`).join('')||'

Todos os condomínios já estão vinculados.

'; + modalBody.innerHTML=``; +} + 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)}};