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 e1a5038..c2b82a9 100644 --- a/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java +++ b/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java @@ -84,6 +84,7 @@ public final class ClienteHandlers implements HttpHandler { if(p.length==4&&"condominios".equals(p[0])&&"espacos-comuns".equals(p[2])&&"PUT".equals(method)){ long cond=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,cond)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} atualizarEspaco(ex,c,cond,id(p[3]),funcionarioId); return; } if(p.length==3&&"condominios".equals(p[0])&&"assembleias".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("POST".equals(method)) criarAssembleia(ex,c,empresaId,cond,funcionarioId); else if("GET".equals(method)) listarParam(ex,c,"SELECT * FROM assembleias WHERE condominio_id=? ORDER BY data_hora DESC",cond,"assembleias"); else HttpResponses.methodNotAllowed(ex); return; } if(p.length==4&&"condominios".equals(p[0])&&"assembleias".equals(p[2])&&"PUT".equals(method)){ long cond=id(p[1]); if(!podeAcessarCondominio(c,cargo,funcionarioId,cond)){HttpResponses.json(ex,403,Map.of("erro","condominio_nao_vinculado"));return;} atualizarAssembleia(ex,c,empresaId,cond,id(p[3]),funcionarioId); return; } + if(p.length==5&&"condominios".equals(p[0])&&"assembleias".equals(p[2])&&"notificar".equals(p[4])&&"POST".equals(method)){ long cond=id(p[1]); notificarAssembleia(ex,c,empresaId,cond,id(p[3]),funcionarioId); return; } if(p.length==3&&"condominios".equals(p[0])&&"documentos".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("POST".equals(method)) uploadDocumento(ex,c,empresaId,cond,funcionarioId); else if("GET".equals(method)) listarParam(ex,c,"SELECT * FROM documentos WHERE condominio_id=? ORDER BY criado_em DESC",cond,"documentos"); else HttpResponses.methodNotAllowed(ex); return; } if(p.length==3&&"documentos".equals(p[0])&&"download".equals(p[2])&&"GET".equals(method)){ downloadDocumento(ex,c,id(p[1])); return; } HttpResponses.json(ex,404,Map.of("erro","endpoint_nao_encontrado")); @@ -201,6 +202,61 @@ public final class ClienteHandlers implements HttpHandler { long aviso=criarAvisoAutomaticoAssembleia(c,cond,aid,fid,b,dests,"Assembleia cancelada","A assembleia foi cancelada.",null); try(PreparedStatement ps=c.prepareStatement("UPDATE avisos SET tipo='cancelamento', prioridade='alta' WHERE id=?")){ps.setLong(1,aviso);ps.executeUpdate();} } + private void notificarAssembleia(HttpExchange ex,Connection c,long emp,long cond,long aid,long fid)throws Exception{ + Map ass=one(c,"SELECT * FROM assembleias WHERE id=? AND condominio_id=?",Arrays.asList(aid,cond)); + if(ass==null){ + HttpResponses.json(ex,404,Map.of("erro","assembleia_nao_encontrada","mensagem","Assembleia não encontrada.")); + return; + } + if(!funcionalidadeAtiva(emp,"whatsapp_envio")){ + HttpResponses.json(ex,400,Map.of("erro","funcionalidade_whatsapp_inativa","mensagem","Envio por WhatsApp não está liberado para esta empresa no painel Master.")); + return; + } + if(evolutionAprovada(emp)==null){ + HttpResponses.json(ex,400,Map.of("erro","instancia_pendente_aprovacao","mensagem","A conexão WhatsApp não está configurada ou ainda não foi aprovada pelo painel Master.")); + return; + } + List padrao=destinatariosPadraoAssembleia(c,emp,cond); + if(padrao.isEmpty()){ + HttpResponses.json(ex,400,Map.of("erro","sem_destinatarios","mensagem","Nenhum contato ou funcionário vinculado para receber a notificação deste condomínio.")); + return; + } + Map aviso=one(c,"SELECT id, status_envio FROM avisos WHERE assembleia_id=? ORDER BY id ASC LIMIT 1",Arrays.asList(aid)); + long avisoId; + if(aviso==null){ + Object dhObj=ass.get("data_hora"); + Timestamp dataHora=dhObj instanceof Timestamp?(Timestamp)dhObj:(dhObj instanceof LocalDateTime?Timestamp.valueOf((LocalDateTime)dhObj):ts(str(dhObj).split("\\.")[0])); + String dataStr=dataHora.toLocalDateTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + Map b=new HashMap<>(); + b.put("data_hora",dataStr); + b.put("local",str(ass.get("local"))); + b.put("pauta",str(ass.get("pauta"))); + avisoId=criarAvisoAutomaticoAssembleia(c,cond,aid,fid,b,padrao,"Assembleia agendada","Nova assembleia agendada.",null); + criarAvisoAutomaticoAssembleia(c,cond,aid,fid,b,padrao,"Lembrete de assembleia","Lembrete: a assembleia acontece amanhã.",Timestamp.valueOf(dataHora.toLocalDateTime().minusDays(1))); + criarAvisoAutomaticoAssembleia(c,cond,aid,fid,b,padrao,"Lembrete de assembleia","Lembrete: a assembleia acontece hoje.",dataHora); + } else { + avisoId=((Number)aviso.get("id")).longValue(); + List> destsExistentes=rows(c,"SELECT destinatario_id FROM aviso_destinatarios WHERE aviso_id=?",Arrays.asList(avisoId)); + if(destsExistentes.isEmpty()){ + for(Destinatario d:padrao){ + insert(c,"INSERT INTO aviso_destinatarios (aviso_id,tipo_destinatario,destinatario_id) VALUES (?,?,?) ON CONFLICT DO NOTHING",Arrays.asList(avisoId,d.tipo(),d.id())); + } + } + try(PreparedStatement ps=c.prepareStatement("UPDATE avisos SET status_envio='nao_enviado', enviar_whatsapp=true WHERE id=? AND condominio_id=?")){ + ps.setLong(1,avisoId); + ps.setLong(2,cond); + ps.executeUpdate(); + } + } + enviarAviso(c,emp,cond,avisoId,false); + Map atualizado=one(c,"SELECT status_envio FROM avisos WHERE id=?",Arrays.asList(avisoId)); + String st=atualizado!=null?str(atualizado.get("status_envio")):"enviado"; + if("falhou".equals(st)){ + HttpResponses.json(ex,200,Map.of("ok",false,"status_envio","falhou","mensagem","Tentativa de envio realizada, porém com falhas no envio. Verifique o status da conexão WhatsApp e os telefones cadastrados.")); + } else { + HttpResponses.json(ex,200,Map.of("ok",true,"status_envio",st,"mensagem","Notificação enviada com sucesso!")); + } + } private void uploadDocumento(HttpExchange ex,Connection c,long emp,long cond,long fid)throws Exception{Multipart mp=parseMultipart(ex); if(mp.bytes.length>MAX_UPLOAD){HttpResponses.json(ex,400,Map.of("erro","arquivo_muito_grande"));return;} String ext=ext(mp.filename); if(!EXT_OK.contains(ext)){HttpResponses.json(ex,400,Map.of("erro","extensao_nao_permitida"));return;} String db=databaseName(emp); Path dir=uploadRoot.resolve(db).resolve(String.valueOf(cond)); Files.createDirectories(dir); String safe=UUID.randomUUID()+"-"+mp.filename.replaceAll("[^a-zA-Z0-9._-]","_"); Path file=dir.resolve(safe); Files.write(file,mp.bytes); String rel=db+"/"+cond+"/"+safe; long id=insert(c,"INSERT INTO documentos (condominio_id,tipo,nome_arquivo,caminho_armazenamento,enviado_por) VALUES (?,?,?,?,?)",Arrays.asList(cond,nullable(mp.tipo),mp.filename,rel,fid)); audit(c,fid,"create","documentos",id,null,Map.of("nome_arquivo",mp.filename)); HttpResponses.json(ex,201,Map.of("documento",one(c,"SELECT * FROM documentos WHERE id=?",Arrays.asList(id))));} private void downloadDocumento(HttpExchange ex,Connection c,long id)throws Exception{Mapd=one(c,"SELECT * FROM documentos WHERE id=?",Arrays.asList(id)); if(d==null){HttpResponses.json(ex,404,Map.of("erro","documento_nao_encontrado"));return;} Path f=uploadRoot.resolve(String.valueOf(d.get("caminho_armazenamento"))); if(!Files.exists(f)){HttpResponses.json(ex,404,Map.of("erro","arquivo_nao_encontrado"));return;} ex.getResponseHeaders().set("Content-Disposition","attachment; filename=\""+d.get("nome_arquivo")+"\""); ex.sendResponseHeaders(200,Files.size(f)); Files.copy(f,ex.getResponseBody()); ex.close();} @@ -396,7 +452,7 @@ public final class ClienteHandlers implements HttpHandler { private void detalheAviso(HttpExchange ex,Connection c,long cond,long aviso)throws Exception{Mapa=one(c,"SELECT * FROM avisos WHERE id=? AND condominio_id=?",Arrays.asList(aviso,cond));if(a==null){HttpResponses.json(ex,404,Map.of("erro","aviso_nao_encontrado"));return;}Map resp=new LinkedHashMap<>();resp.put("aviso",a);resp.put("documentos",rows(c,"SELECT ad.*, d.nome_arquivo, d.caminho_armazenamento FROM aviso_documentos ad JOIN documentos d ON d.id=ad.documento_id WHERE ad.aviso_id=?",Arrays.asList(aviso)));resp.put("destinatarios",rows(c,"SELECT * FROM aviso_destinatarios WHERE aviso_id=? ORDER BY tipo_destinatario,destinatario_id",Arrays.asList(aviso)));HttpResponses.json(ex,200,resp);} private void reenviarAviso(HttpExchange ex,Connection c,long emp,long cond,long aviso)throws Exception{try(PreparedStatement ps=c.prepareStatement("UPDATE avisos SET status_envio='nao_enviado' WHERE id=? AND condominio_id=?")){ps.setLong(1,aviso);ps.setLong(2,cond);if(ps.executeUpdate()==0){HttpResponses.json(ex,404,Map.of("erro","aviso_nao_encontrado"));return;}}enviarAviso(c,emp,cond,aviso,false);detalheAviso(ex,c,cond,aviso);} private void excluirAviso(HttpExchange ex,Connection c,long emp,long cond,long aviso,long fid)throws Exception{Map old=one(c,"SELECT * FROM avisos WHERE id=? AND condominio_id=?",Arrays.asList(aviso,cond));if(old==null){HttpResponses.json(ex,404,Map.of("erro","aviso_nao_encontrado"));return;}c.setAutoCommit(false);try{try(PreparedStatement ps=c.prepareStatement("DELETE FROM aviso_documentos WHERE aviso_id=?")){ps.setLong(1,aviso);ps.executeUpdate();}try(PreparedStatement ps=c.prepareStatement("DELETE FROM aviso_destinatarios WHERE aviso_id=?")){ps.setLong(1,aviso);ps.executeUpdate();}try(PreparedStatement ps=c.prepareStatement("DELETE FROM avisos WHERE id=? AND condominio_id=?")){ps.setLong(1,aviso);ps.setLong(2,cond);ps.executeUpdate();}audit(c,fid,"delete","avisos",aviso,old,Map.of("ok",true));c.commit();try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("DELETE FROM logs_envio_avisos WHERE empresa_id=? AND condominio_id=? AND aviso_id=?")){ps.setLong(1,emp);ps.setLong(2,cond);ps.setLong(3,aviso);ps.executeUpdate();}HttpResponses.json(ex,200,Map.of("ok",true));}catch(Exception err){c.rollback();throw err;}finally{c.setAutoCommit(true);}} - private void enviarAviso(Connection c,long emp,long cond,long aviso,boolean apenasFalhos)throws Exception{Mapa=one(c,"SELECT * FROM avisos WHERE id=? AND condominio_id=?",Arrays.asList(aviso,cond));if(a==null)return;boolean w=Boolean.TRUE.equals(a.get("enviar_whatsapp"));boolean e=Boolean.TRUE.equals(a.get("enviar_email"));Map inst=w?evolutionAprovada(emp):null;Map smtp=e?smtpConfig():Map.of();List> dests=rows(c,"SELECT * FROM aviso_destinatarios WHERE aviso_id=?",Arrays.asList(aviso));boolean allOk=true;int tentativas=0;if(dests.isEmpty())allOk=false;for(Mapd:dests){String tipo=String.valueOf(d.get("tipo_destinatario"));long did=((Number)d.get("destinatario_id")).longValue();Map contato="funcionario".equals(tipo)?funcionario(emp,did):("extra".equals(tipo)?one(c,"SELECT id,nome,telefone,email FROM destinatarios_extra WHERE id=? AND status='ativo'",Arrays.asList(did)):("assembleia".equals(tipo)?one(c,"SELECT id,nome,telefone,email FROM assembleia_responsaveis WHERE id=? AND status='ativo'",Arrays.asList(did)):one(c,"SELECT id,nome,telefone,email FROM contatos WHERE id=?",Arrays.asList(did))));if(contato==null){allOk=false;continue;}String nome=String.valueOf(contato.get("nome"));if(w&&deveTentar(emp,aviso,did,"whatsapp",apenasFalhos)){tentativas++;String tel=telefoneWhatsapp(contato.get("telefone"));try{if(tel.length()<12)throw new IllegalArgumentException("telefone_invalido");String[] parts=String.valueOf(inst.get("instance_token_enc")).split(":",2);String resposta=evolutionApiClient.enviarTexto(str(inst.get("instance_url")),parts[0],parts.length>1?parts[1]:"",tel,msg(a));logEnvio(emp,cond,aviso,did,nome,"whatsapp","enviado",limite(resposta));}catch(Exception err){allOk=false;logEnvio(emp,cond,aviso,did,nome,"whatsapp","falhou",err.getMessage());}}if(e&&deveTentar(emp,aviso,did,"email",apenasFalhos)){tentativas++;String email=str(contato.get("email"));try{if(!email.contains("@"))throw new IllegalArgumentException("email_invalido");emailClient.enviar(smtp,email,str(a.get("titulo")),msg(a));logEnvio(emp,cond,aviso,did,nome,"email","enviado",null);}catch(Exception err){allOk=false;logEnvio(emp,cond,aviso,did,nome,"email","falhou",err.getMessage());}}}if(allOk&&tentativas>0&&reagendarAvisoRecorrente(c,aviso,a))return;try(PreparedStatement ps=c.prepareStatement("UPDATE avisos SET status_envio=?, ultimo_envio=CASE WHEN ? THEN now() ELSE ultimo_envio END WHERE id=?")){boolean ok=allOk&&tentativas>0;ps.setString(1,ok?"enviado":"falhou");ps.setBoolean(2,ok);ps.setLong(3,aviso);ps.executeUpdate();}} + private void enviarAviso(Connection c,long emp,long cond,long aviso,boolean apenasFalhos)throws Exception{Mapa=one(c,"SELECT * FROM avisos WHERE id=? AND condominio_id=?",Arrays.asList(aviso,cond));if(a==null)return;boolean w=Boolean.TRUE.equals(a.get("enviar_whatsapp"));boolean e=Boolean.TRUE.equals(a.get("enviar_email"));Map inst=w?evolutionAprovada(emp):null;Map smtp=e?smtpConfig():Map.of();List> dests=rows(c,"SELECT * FROM aviso_destinatarios WHERE aviso_id=?",Arrays.asList(aviso));boolean allOk=true;int tentativas=0;if(dests.isEmpty())allOk=false;for(Mapd:dests){String tipo=String.valueOf(d.get("tipo_destinatario"));long did=((Number)d.get("destinatario_id")).longValue();Map contato="funcionario".equals(tipo)?funcionario(emp,did):("extra".equals(tipo)?one(c,"SELECT id,nome,telefone,email FROM destinatarios_extra WHERE id=? AND status='ativo'",Arrays.asList(did)):("assembleia".equals(tipo)?one(c,"SELECT id,nome,telefone,email FROM assembleia_responsaveis WHERE id=? AND status='ativo'",Arrays.asList(did)):one(c,"SELECT id,nome,telefone,email FROM contatos WHERE id=?",Arrays.asList(did))));if(contato==null){allOk=false;continue;}String nome=String.valueOf(contato.get("nome"));if(w&&deveTentar(emp,aviso,did,"whatsapp",apenasFalhos)){tentativas++;String tel=telefoneWhatsapp(contato.get("telefone"));try{if(tel.length()<12)throw new IllegalArgumentException("telefone_invalido");if(inst==null)throw new IllegalArgumentException("instancia_whatsapp_nao_encontrada");String[] parts=String.valueOf(inst.get("instance_token_enc")).split(":",2);String resposta=evolutionApiClient.enviarTexto(str(inst.get("instance_url")),parts[0],parts.length>1?parts[1]:"",tel,msg(a));logEnvio(emp,cond,aviso,did,nome,"whatsapp","enviado",limite(resposta));}catch(Exception err){allOk=false;logEnvio(emp,cond,aviso,did,nome,"whatsapp","falhou",err.getMessage());}}if(e&&deveTentar(emp,aviso,did,"email",apenasFalhos)){tentativas++;String email=str(contato.get("email"));try{if(!email.contains("@"))throw new IllegalArgumentException("email_invalido");emailClient.enviar(smtp,email,str(a.get("titulo")),msg(a));logEnvio(emp,cond,aviso,did,nome,"email","enviado",null);}catch(Exception err){allOk=false;logEnvio(emp,cond,aviso,did,nome,"email","falhou",err.getMessage());}}}if(allOk&&tentativas>0&&reagendarAvisoRecorrente(c,aviso,a))return;try(PreparedStatement ps=c.prepareStatement("UPDATE avisos SET status_envio=?, ultimo_envio=CASE WHEN ? THEN now() ELSE ultimo_envio END WHERE id=?")){boolean ok=allOk&&tentativas>0;ps.setString(1,ok?"enviado":"falhou");ps.setBoolean(2,ok);ps.setLong(3,aviso);ps.executeUpdate();}} private boolean reagendarAvisoRecorrente(Connection c,long aviso,Mapa)throws SQLException{ int meses=a.get("recorrencia_meses")==null?0:((Number)a.get("recorrencia_meses")).intValue(); if(!Boolean.TRUE.equals(a.get("recorrencia_ativa"))||meses<=0)return false; diff --git a/src/main/webapp/cliente/assembleias.html b/src/main/webapp/cliente/assembleias.html index 9f70ef5..61385ab 100644 --- a/src/main/webapp/cliente/assembleias.html +++ b/src/main/webapp/cliente/assembleias.html @@ -1 +1 @@ -Assembleia

Assembleia

Todas as assembleias

CondominioTipoDataLocalStatusNotificados

Responsáveis pela assembleia

NomeWhatsAppE-mailDias disponíveisStatusAções
+Assembleia

Assembleia

Todas as assembleias

CondominioTipoDataLocalStatusNotificadosAções

Responsáveis pela assembleia

NomeWhatsAppE-mailDias disponíveisStatusAções
diff --git a/src/main/webapp/cliente/assembleias.js b/src/main/webapp/cliente/assembleias.js index ed41749..39464ab 100644 --- a/src/main/webapp/cliente/assembleias.js +++ b/src/main/webapp/cliente/assembleias.js @@ -18,7 +18,7 @@ function formHtml(r={}){ function renderAssembleias(){ const q=termo(); const lista=assembleias.filter(a=>contem(q,a.condominio_nome,a.tipo,a.data_hora,a.local,a.status,destinatariosLabel(a))); - rows.innerHTML=lista.map(a=>`${esc(a.condominio_nome||'-')}${esc(a.tipo)}${Util.date(a.data_hora)}${esc(a.local||'')}${Util.badge(a.status)}${esc(destinatariosLabel(a))}Abrir`).join('')||'Nenhuma assembleia encontrada.'; + rows.innerHTML=lista.map(a=>`${esc(a.condominio_nome||'-')}${esc(a.tipo)}${Util.date(a.data_hora)}${esc(a.local||'')}${Util.badge(a.status)}${esc(destinatariosLabel(a))}`).join('')||'Nenhuma assembleia encontrada.'; } function ensureCalendarModal(){let m=document.querySelector('#assembleia-calendar-modal');if(m)return m;document.body.insertAdjacentHTML('beforeend','');return document.querySelector('#assembleia-calendar-modal')} function renderCalendarioAssembleias(selected){ensureCalendarModal();const title=document.querySelector('#calendar-title'),grid=document.querySelector('#calendar-grid');title.textContent=monthLabel(calMonth);const y=calMonth.getFullYear(),m=calMonth.getMonth(),start=new Date(y,m,1-new Date(y,m,1).getDay()),todayKey=dateKey(new Date());let html=['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'].map(x=>'
'+x+'
').join('');for(let i=0;i<42;i++){const d=new Date(start);d.setDate(start.getDate()+i);const key=dateKey(d),items=assembleias.filter(a=>dateKey(a.data_hora)===key),classes=['calendar-day'];if(d.getMonth()!==m)classes.push('other');if(items.length)classes.push('busy');if(key===todayKey)classes.push('today');html+=''}grid.innerHTML=html;grid.querySelectorAll('.calendar-day').forEach(btn=>btn.onclick=()=>selecionarDiaCalendario(btn.dataset.date));if(selected)selecionarDiaCalendario(selected,false)} @@ -68,7 +68,8 @@ window.alternarBuscaAssembleias=()=>{ window.abrirCalendarioAssembleias=async()=>{try{if(!assembleias.length)await load();calMonth=new Date();ensureCalendarModal().classList.remove('hidden');renderCalendarioAssembleias()}catch(e){Util.toast(e.message,true)}}; window.fecharCalendarioAssembleias=()=>document.querySelector('#assembleia-calendar-modal')?.classList.add('hidden'); window.mudarMesCalendario=delta=>{calMonth=new Date(calMonth.getFullYear(),calMonth.getMonth()+delta,1);renderCalendarioAssembleias()}; -window.selecionarDiaCalendario=(key,mark=true)=>{if(mark)document.querySelectorAll('.calendar-day').forEach(x=>x.classList.toggle('selected',x.dataset.date===key));const list=document.querySelector('#calendar-day-list'),title=document.querySelector('#calendar-day-title'),items=assembleias.filter(a=>dateKey(a.data_hora)===key);const [yy,mm,dd]=key.split('-');title.textContent=dd+'/'+mm+'/'+yy;if(!items.length){list.innerHTML='

Nenhuma assembleia agendada neste dia.

';return;}list.innerHTML=items.map(a=>'
'+esc(a.condominio_nome||'-')+''+Util.date(a.data_hora)+''+esc(a.tipo)+' - '+esc(a.local||'-')+'

'+esc(destinatariosLabel(a))+'

Abrir condomínio
').join('')}; +window.selecionarDiaCalendario=(key,mark=true)=>{if(mark)document.querySelectorAll('.calendar-day').forEach(x=>x.classList.toggle('selected',x.dataset.date===key));const list=document.querySelector('#calendar-day-list'),title=document.querySelector('#calendar-day-title'),items=assembleias.filter(a=>dateKey(a.data_hora)===key);const [yy,mm,dd]=key.split('-');title.textContent=dd+'/'+mm+'/'+yy;if(!items.length){list.innerHTML='

Nenhuma assembleia agendada neste dia.

';return;}list.innerHTML=items.map(a=>'
'+esc(a.condominio_nome||'-')+''+Util.date(a.data_hora)+''+esc(a.tipo)+' - '+esc(a.local||'-')+'

'+esc(destinatariosLabel(a))+'

').join('')}; +window.enviarNotificacaoAssembleia=async(btn,condominioId,assembleiaId)=>{if(!await Util.confirm('Deseja enviar a notificação para os contatos e responsáveis desta assembleia?','Enviar notificação'))return;const original=btn?btn.textContent:'';if(btn){btn.disabled=true;btn.textContent='Enviando...';}try{const res=await Api.post(`/api/condominios/${condominioId}/assembleias/${assembleiaId}/notificar`,{});if(res&&res.ok===false){Util.toast(res.mensagem||'Falha ao enviar notificação.',true);}else{Util.toast(res?.mensagem||'Notificação enviada com sucesso!');}await load();}catch(err){Util.toast(err.message,true);}finally{if(btn){btn.disabled=false;btn.textContent=original;}}}; window.abrirNovoResponsavel=()=>abrirResponsavel({}); window.editarResponsavel=id=>{const r=responsaveis.find(x=>Number(x.id)===Number(id));if(r)abrirResponsavel(r)}; window.fecharResponsavelModal=()=>{modal.classList.add('hidden');modalBody.innerHTML=''}; diff --git a/src/main/webapp/cliente/condominio-detalhe.js b/src/main/webapp/cliente/condominio-detalhe.js index 02f0ad8..ca3ac86 100644 --- a/src/main/webapp/cliente/condominio-detalhe.js +++ b/src/main/webapp/cliente/condominio-detalhe.js @@ -18,7 +18,7 @@ window.abrirCalendarioAssembleias=async()=>{try{const d=await Api.get('/api/asse window.fecharCalendarioAssembleias=()=>document.querySelector('#assembleia-calendar-modal')?.classList.add('hidden'); window.mudarMesCalendario=delta=>{calMonth=new Date(calMonth.getFullYear(),calMonth.getMonth()+delta,1);renderCalendarioAssembleias()}; window.selecionarDiaCalendario=(key,mark=true)=>{if(mark)document.querySelectorAll('.calendar-day').forEach(x=>x.classList.toggle('selected',x.dataset.date===key));const list=document.querySelector('#calendar-day-list'),title=document.querySelector('#calendar-day-title'),items=assembleiasCalendario.filter(a=>dateKey(a.data_hora)===key);const [yy,mm,dd]=key.split('-');title.textContent=dd+'/'+mm+'/'+yy;if(!items.length){list.innerHTML='

Nenhuma assembleia agendada neste dia.

';return;}list.innerHTML=items.map(a=>{const dest=(a.destinatarios||[]).map(x=>x.nome+' ('+x.tipo+')').join(', ')||'Nenhum destinatário registrado';return '
'+a.condominio_nome+''+Util.date(a.data_hora)+''+a.tipo+' - '+(a.local||'-')+'

'+dest+'

'}).join('')}; -window.reenviarAvisoAssembleia=async(cond,aid)=>{try{const a=assembleiasCalendario.find(x=>Number(x.id)===Number(aid));if(!a||!a.aviso_id)throw new Error('Aviso da assembleia não encontrado.');await Api.post('/api/condominios/'+cond+'/avisos/'+a.aviso_id+'/reenviar',{});Util.toast('Notificação reenviada')}catch(e){Util.toast(e.message,true)}}; +window.reenviarAvisoAssembleia=async(cond,aid)=>{try{const res=await Api.post('/api/condominios/'+cond+'/assembleias/'+aid+'/notificar',{});if(res&&res.ok===false){Util.toast(res.mensagem||'Falha ao enviar notificação.',true);}else{Util.toast(res?.mensagem||'Notificação enviada com sucesso!');}await abrirCalendarioAssembleias();await loadAssembleias();await load();}catch(e){Util.toast(e.message,true)}}; window.cancelarAssembleiaCalendario=async(cond,aid)=>{if(!await Util.confirm('Cancelar esta assembleia?','Cancelar assembleia'))return;try{await Api.put('/api/condominios/'+cond+'/assembleias/'+aid,{status:'cancelada'});Util.toast('Assembleia cancelada');await abrirCalendarioAssembleias();await loadAssembleias();await load()}catch(e){Util.toast(e.message,true)}}; function ensureAvisoNotifModal(){let m=document.querySelector('#aviso-notif-modal');if(m)return m;document.body.insertAdjacentHTML('beforeend','');return document.querySelector('#aviso-notif-modal')} function avisoDestInputs(){return [...document.querySelectorAll('#aviso-dests-hidden input[name=dest]')]}