From 1791e925977633f0171b8550a2f7a47215c59dff Mon Sep 17 00:00:00 2001 From: Tiago Date: Wed, 30 Sep 2026 14:56:44 -0300 Subject: [PATCH] Automatiza notificacoes de assembleias --- .../tenant/http/ClienteHandlers.java | 83 +++++++++++++++---- src/main/webapp/cliente/condominio-detalhe.js | 6 +- src/main/webapp/css/app.css | 2 + 3 files changed, 70 insertions(+), 21 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 2a69f6a..1ec0d02 100644 --- a/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java +++ b/src/main/java/com/seconectai/condocontab/tenant/http/ClienteHandlers.java @@ -108,32 +108,79 @@ public final class ClienteHandlers implements HttpHandler { private void criarEspaco(HttpExchange ex,Connection c,long cond,long fid)throws Exception{Mapb=body(ex); long id=insert(c,"INSERT INTO espacos_comuns (condominio_id,nome,descricao,valor_utilizacao_centavos,indice_correcao) VALUES (?,?,?,?,?)",Arrays.asList(cond,req(b,"nome"),nullable(b.get("descricao")),optLong(b.get("valor_utilizacao_centavos")),nullable(b.get("indice_correcao")))); audit(c,fid,"create","espacos_comuns",id,null,b); HttpResponses.json(ex,201,Map.of("espaco",one(c,"SELECT * FROM espacos_comuns WHERE id=?",Arrays.asList(id))));} private void atualizarEspaco(HttpExchange ex,Connection c,long cond,long eid,long fid)throws Exception{Mapb=body(ex); Map old=one(c,"SELECT * FROM espacos_comuns WHERE id=? AND condominio_id=?",Arrays.asList(eid,cond)); if(old==null){HttpResponses.json(ex,404,Map.of("erro","espaco_nao_encontrado"));return;} Long novo=optLong(b.get("valor_utilizacao_centavos")); try(PreparedStatement ps=c.prepareStatement("UPDATE espacos_comuns SET nome=COALESCE(?,nome), descricao=COALESCE(?,descricao), valor_utilizacao_centavos=COALESCE(?,valor_utilizacao_centavos), indice_correcao=COALESCE(?,indice_correcao), data_ultimo_reajuste=CASE WHEN ? IS NOT NULL AND ?<>valor_utilizacao_centavos THEN current_date ELSE data_ultimo_reajuste END WHERE id=? AND condominio_id=?")){ps.setString(1,b.get("nome"));ps.setString(2,b.get("descricao")); if(novo==null)ps.setNull(3,Types.BIGINT);else ps.setLong(3,novo); ps.setString(4,b.get("indice_correcao")); if(novo==null){ps.setNull(5,Types.BIGINT);ps.setNull(6,Types.BIGINT);}else{ps.setLong(5,novo);ps.setLong(6,novo);} ps.setLong(7,eid);ps.setLong(8,cond);ps.executeUpdate();} audit(c,fid,"update","espacos_comuns",eid,old,b); HttpResponses.json(ex,200,Map.of("espaco",one(c,"SELECT * FROM espacos_comuns WHERE id=?",Arrays.asList(eid))));} private void criarAssembleia(HttpExchange ex,Connection c,long emp,long cond,long fid)throws Exception{ - Mapb=body(ex); - long id=insert(c,"INSERT INTO assembleias (condominio_id,tipo,data_hora,local,pauta) VALUES (?,?,?,?,?)",Arrays.asList(cond,req(b,"tipo"),ts(req(b,"data_hora")),nullable(b.get("local")),nullable(b.get("pauta")))); - audit(c,fid,"create","assembleias",id,null,b); - Map envio=criarAvisoAssembleia(c,emp,cond,id,fid,b); + String raw=rawBody(ex); + Mapb=SimpleJson.parseObject(removeArrayField(raw,"destinatarios")); + Listdests=destinatarios(raw); + if(raw.contains("\"destinatarios\"")&&dests.isEmpty()){HttpResponses.json(ex,400,Map.of("erro","destinatario_obrigatorio","mensagem","Selecione ao menos um membro para receber as mensagens da assembleia."));return;} + long id; + Map envio; + c.setAutoCommit(false); + try{ + id=insert(c,"INSERT INTO assembleias (condominio_id,tipo,data_hora,local,pauta) VALUES (?,?,?,?,?)",Arrays.asList(cond,req(b,"tipo"),ts(req(b,"data_hora")),nullable(b.get("local")),nullable(b.get("pauta")))); + audit(c,fid,"create","assembleias",id,null,b); + envio=criarAvisosAssembleia(c,emp,cond,id,fid,b,dests); + c.commit(); + }catch(Exception err){ + c.rollback(); + throw err; + }finally{ + c.setAutoCommit(true); + } HttpResponses.json(ex,201,Map.of("assembleia",one(c,"SELECT * FROM assembleias WHERE id=?",Arrays.asList(id)),"envio",envio)); } - private Map criarAvisoAssembleia(Connection c,long emp,long cond,long assembleia,long fid,Mapb)throws Exception{ - List> contatosAviso=rows(c,"SELECT contato_id FROM contato_condominio WHERE condominio_id=? AND status=?",Arrays.asList(cond,"ativo")); - List> funcionariosAviso=rows(c,"SELECT funcionario_id FROM funcionario_condominio WHERE condominio_id=?",Arrays.asList(cond)); - int destinatarios=contatosAviso.size()+funcionariosAviso.size(); - if(destinatarios==0)return Map.of("enviados",0,"falhas",0,"mensagem","Nenhum destinatario vinculado ao condominio."); + private Map criarAvisosAssembleia(Connection c,long emp,long cond,long assembleia,long fid,Mapb,Listdests)throws Exception{ + List destinatarios=dests.isEmpty()?destinatariosPadraoAssembleia(c,cond):dests; + int total=destinatarios.size(); + if(total==0)return Map.of("enviados",0,"falhas",0,"mensagem","Nenhum destinatario vinculado ao condominio."); if(!funcionalidadeAtiva(emp,"whatsapp_envio"))return Map.of("enviados",0,"falhas",1,"mensagem","WhatsApp nao liberado para esta empresa."); if(evolutionAprovada(emp)==null)return Map.of("enviados",0,"falhas",1,"mensagem","WhatsApp nao aprovado para esta empresa."); - String titulo="Assembleia " + req(b,"tipo"); - StringBuilder mensagem=new StringBuilder(); - mensagem.append("Nova assembleia agendada."); + Timestamp data=ts(req(b,"data_hora")); + List avisos=new ArrayList<>(); + avisos.add(criarAvisoAutomaticoAssembleia(c,cond,assembleia,fid,b,destinatarios,"Assembleia agendada","Nova assembleia agendada.",null)); + avisos.add(criarAvisoAutomaticoAssembleia(c,cond,assembleia,fid,b,destinatarios,"Lembrete de assembleia","Lembrete: a assembleia acontece amanha.",Timestamp.valueOf(data.toLocalDateTime().minusDays(1)))); + avisos.add(criarAvisoAutomaticoAssembleia(c,cond,assembleia,fid,b,destinatarios,"Lembrete de assembleia","Lembrete: a assembleia acontece hoje.",data)); + return Map.of("aviso_ids",avisos,"destinatarios",total,"status","enfileirado"); + } + private List destinatariosPadraoAssembleia(Connection c,long cond)throws SQLException{ + List out=new ArrayList<>(); + for(Map row:rows(c,"SELECT contato_id FROM contato_condominio WHERE condominio_id=? AND status=?",Arrays.asList(cond,"ativo")))out.add(new Destinatario("contato",((Number)row.get("contato_id")).longValue())); + for(Map row:rows(c,"SELECT funcionario_id FROM funcionario_condominio WHERE condominio_id=?",Arrays.asList(cond)))out.add(new Destinatario("funcionario",((Number)row.get("funcionario_id")).longValue())); + return out; + } + private long criarAvisoAutomaticoAssembleia(Connection c,long cond,long assembleia,long fid,Mapb,Listdests,String titulo,String intro,Timestamp envio)throws SQLException{ + Timestamp agora=Timestamp.valueOf(LocalDateTime.now(java.time.ZoneId.of("America/Sao_Paulo"))); + Timestamp agendado=envio!=null&&envio.before(agora)?agora:envio; + String mensagem=mensagemAssembleia(intro,b); + long aviso=insert(c,"INSERT INTO avisos (condominio_id,assembleia_id,tipo,titulo,mensagem,data_evento,prioridade,enviar_whatsapp,enviar_email,status_envio,criado_por_funcionario) VALUES (?,?,?,?,?,?,?,?,?,?,?)",Arrays.asList(cond,assembleia,"assembleia",titulo,mensagem,agendado,"normal",true,false,agendado!=null&&agendado.after(agora)?"agendado":"nao_enviado",fid)); + for(Destinatario d:dests)insert(c,"INSERT INTO aviso_destinatarios (aviso_id,tipo_destinatario,destinatario_id) VALUES (?,?,?) ON CONFLICT DO NOTHING",Arrays.asList(aviso,d.tipo(),d.id())); + enfileirarAviso(c,aviso,agendado); + return aviso; + } + private String mensagemAssembleia(String intro,Mapb){ + StringBuilder mensagem=new StringBuilder(intro); mensagem.append("\nData/hora: ").append(req(b,"data_hora").replace('T',' ')); if(b.get("local")!=null&&!b.get("local").isBlank())mensagem.append("\nLocal: ").append(b.get("local")); if(b.get("pauta")!=null&&!b.get("pauta").isBlank())mensagem.append("\nPauta: ").append(b.get("pauta")); - long aviso=insert(c,"INSERT INTO avisos (condominio_id,assembleia_id,tipo,titulo,mensagem,data_evento,prioridade,enviar_whatsapp,enviar_email,status_envio,criado_por_funcionario) VALUES (?,?,?,?,?,?,?,?,?,?,?)",Arrays.asList(cond,assembleia,"assembleia",titulo,mensagem.toString(),ts(b.get("data_hora")),"normal",true,false,"nao_enviado",fid)); - for(Map row:contatosAviso)insert(c,"INSERT INTO aviso_destinatarios (aviso_id,tipo_destinatario,destinatario_id) VALUES (?,?,?) ON CONFLICT DO NOTHING",Arrays.asList(aviso,"contato",((Number)row.get("contato_id")).longValue())); - for(Map row:funcionariosAviso)insert(c,"INSERT INTO aviso_destinatarios (aviso_id,tipo_destinatario,destinatario_id) VALUES (?,?,?) ON CONFLICT DO NOTHING",Arrays.asList(aviso,"funcionario",((Number)row.get("funcionario_id")).longValue())); - enfileirarAviso(c,aviso,ts(b.get("data_hora"))); - return Map.of("aviso_id",aviso,"destinatarios",destinatarios,"status","enfileirado"); + return mensagem.toString(); + } + private void atualizarAssembleia(HttpExchange ex,Connection c,long cond,long aid,long fid)throws Exception{Mapb=body(ex); Map old=one(c,"SELECT * FROM assembleias WHERE id=? AND condominio_id=?",Arrays.asList(aid,cond)); if(old==null){HttpResponses.json(ex,404,Map.of("erro","assembleia_nao_encontrada"));return;} c.setAutoCommit(false);try{try(PreparedStatement ps=c.prepareStatement("UPDATE assembleias SET tipo=COALESCE(?,tipo), data_hora=COALESCE(?,data_hora), local=COALESCE(?,local), pauta=COALESCE(?,pauta), status=COALESCE(?,status) WHERE id=? AND condominio_id=?")){ps.setString(1,b.get("tipo")); if(b.get("data_hora")==null)ps.setNull(2,Types.TIMESTAMP);else ps.setTimestamp(2,ts(b.get("data_hora"))); ps.setString(3,b.get("local"));ps.setString(4,b.get("pauta"));ps.setString(5,b.get("status"));ps.setLong(6,aid);ps.setLong(7,cond);ps.executeUpdate();} if("cancelada".equalsIgnoreCase(b.get("status"))&&!"cancelada".equalsIgnoreCase(str(old.get("status"))))criarAvisoCancelamentoAssembleia(c,cond,aid,fid,old); audit(c,fid,"update","assembleias",aid,old,b); c.commit();HttpResponses.json(ex,200,Map.of("assembleia",one(c,"SELECT * FROM assembleias WHERE id=?",Arrays.asList(aid))));}catch(Exception err){c.rollback();throw err;}finally{c.setAutoCommit(true);}} + private void criarAvisoCancelamentoAssembleia(Connection c,long cond,long aid,long fid,Mapold)throws SQLException{ + List> avisos=rows(c,"SELECT id FROM avisos WHERE assembleia_id=?",Arrays.asList(aid)); + Set vistos=new HashSet<>(); + Listdests=new ArrayList<>(); + for(Mapa:avisos){ + for(Mapd:rows(c,"SELECT tipo_destinatario,destinatario_id FROM aviso_destinatarios WHERE aviso_id=?",Arrays.asList(((Number)a.get("id")).longValue()))){ + String tipo=str(d.get("tipo_destinatario"));long id=((Number)d.get("destinatario_id")).longValue();String key=tipo+":"+id; + if(vistos.add(key))dests.add(new Destinatario(tipo,id)); + } + } + if(dests.isEmpty())dests=destinatariosPadraoAssembleia(c,cond); + try(PreparedStatement ps=c.prepareStatement("UPDATE avisos SET status_envio='cancelado' WHERE assembleia_id=? AND status_envio IN ('nao_enviado','agendado','falhou')")){ps.setLong(1,aid);ps.executeUpdate();} + try(PreparedStatement ps=c.prepareStatement("UPDATE fila_envio SET status='ignorado', ultimo_erro='Assembleia cancelada', locked_at=NULL, atualizado_em=now() WHERE tipo='aviso' AND referencia_id IN (SELECT id FROM avisos WHERE assembleia_id=?) AND status IN ('pendente','processando','falhou')")){ps.setLong(1,aid);ps.executeUpdate();} + Mapb=new HashMap<>();b.put("data_hora",String.valueOf(old.get("data_hora")));b.put("local",str(old.get("local")));b.put("pauta",str(old.get("pauta"))); + 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 atualizarAssembleia(HttpExchange ex,Connection c,long cond,long aid,long fid)throws Exception{Mapb=body(ex); Map old=one(c,"SELECT * FROM assembleias WHERE id=? AND condominio_id=?",Arrays.asList(aid,cond)); if(old==null){HttpResponses.json(ex,404,Map.of("erro","assembleia_nao_encontrada"));return;} try(PreparedStatement ps=c.prepareStatement("UPDATE assembleias SET tipo=COALESCE(?,tipo), data_hora=COALESCE(?,data_hora), local=COALESCE(?,local), pauta=COALESCE(?,pauta), status=COALESCE(?,status) WHERE id=? AND condominio_id=?")){ps.setString(1,b.get("tipo")); if(b.get("data_hora")==null)ps.setNull(2,Types.TIMESTAMP);else ps.setTimestamp(2,ts(b.get("data_hora"))); ps.setString(3,b.get("local"));ps.setString(4,b.get("pauta"));ps.setString(5,b.get("status"));ps.setLong(6,aid);ps.setLong(7,cond);ps.executeUpdate();} audit(c,fid,"update","assembleias",aid,old,b); HttpResponses.json(ex,200,Map.of("assembleia",one(c,"SELECT * FROM assembleias WHERE id=?",Arrays.asList(aid))));} 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();} diff --git a/src/main/webapp/cliente/condominio-detalhe.js b/src/main/webapp/cliente/condominio-detalhe.js index efa8556..c0b3749 100644 --- a/src/main/webapp/cliente/condominio-detalhe.js +++ b/src/main/webapp/cliente/condominio-detalhe.js @@ -17,14 +17,14 @@ window.abrirAvisoNotificacao=()=>{const m=ensureAvisoNotifModal(),ids=new Set(av window.fecharAvisoNotificacao=()=>document.querySelector('#aviso-notif-modal')?.classList.add('hidden'); window.salvarAvisoNotificacao=()=>{const ids=new Set([...document.querySelectorAll('#aviso-notif-body input:checked')].map(x=>x.value));avisoDestInputs().forEach(x=>x.checked=ids.has(x.value));updateAvisoNotifSummary();fecharAvisoNotificacao()}; async function safeLoad(section,fn){try{await fn()}catch(e){document.querySelector('#'+section).innerHTML=`

${e.message}

`;Util.toast(e.message,true)}} -async function load(){try{const d=await Api.get(`/api/condominios/${id}`);document.querySelector('#header').innerHTML=`

${d.condominio.nome}

${d.condominio.endereco||''}

Sindico${d.sindico_atual?.contato_nome||'-'}
Assembleias futuras${d.assembleias_futuras||0}
Avisos ativos${d.avisos_ativos||0}
`;await safeLoad('contatos',loadContatos);await safeLoad('espacos',loadEspacos);await safeLoad('assembleias',loadAssembleias);await safeLoad('documentos',loadDocs);await safeLoad('avisos',loadFuncionariosVinculados);await safeLoad('avisos',loadExtras);await safeLoad('avisos',loadAvisos)}catch(e){Util.toast(e.message,true)}} +async function load(){try{const d=await Api.get(`/api/condominios/${id}`);document.querySelector('#header').innerHTML=`

${d.condominio.nome}

${d.condominio.endereco||''}

Sindico${d.sindico_atual?.contato_nome||'-'}
Assembleias futuras${d.assembleias_futuras||0}
Avisos ativos${d.avisos_ativos||0}
`;await safeLoad('contatos',loadContatos);await safeLoad('avisos',loadFuncionariosVinculados);await safeLoad('avisos',loadExtras);await safeLoad('espacos',loadEspacos);await safeLoad('assembleias',loadAssembleias);await safeLoad('documentos',loadDocs);await safeLoad('avisos',loadAvisos)}catch(e){Util.toast(e.message,true)}} async function loadContatos(){const d=await Api.get(`/api/condominios/${id}/contatos`);contatos=d.contatos||[];document.querySelector('#contatos').innerHTML=`

Contatos

${contatos.map(c=>``).join('')}
NomeTipoTelefoneEmail
${c.contato_nome}${c.tipo}${c.contato_telefone||''}${c.contato_email||''}
`;document.querySelector('#contato-form').onsubmit=async e=>{e.preventDefault();try{await Api.post(`/api/condominios/${id}/contatos/vincular`,Util.formData(e.target));Util.toast('Contato salvo');loadContatos()}catch(err){Util.toast(err.message,true)}}} async function loadEspacos(){const d=await Api.get(`/api/condominios/${id}/espacos-comuns`);document.querySelector('#espacos').innerHTML=`

Espacos comuns

${(d.espacos||[]).map(x=>``).join('')}
NomeValorIndiceDescricao
${x.nome}${Util.money(x.valor_utilizacao_centavos)}${x.indice_correcao||''}${x.descricao||''}
`;document.querySelector('#espaco-form').onsubmit=async e=>{e.preventDefault();try{await Api.post(`/api/condominios/${id}/espacos-comuns`,Util.formData(e.target));Util.toast('Espaco salvo');loadEspacos()}catch(err){Util.toast(err.message,true)}}} -async function loadAssembleias(){const d=await Api.get(`/api/condominios/${id}/assembleias`);document.querySelector('#assembleias').innerHTML=`

Assembleias

${(d.assembleias||[]).map(a=>``).join('')}
TipoDataLocalStatus
${a.tipo}${Util.date(a.data_hora)}${a.local||''}${Util.badge(a.status)}
`;document.querySelector('#ass-form').onsubmit=async e=>{e.preventDefault();try{await Api.post(`/api/condominios/${id}/assembleias`,Util.formData(e.target));Util.toast('Assembleia salva');loadAssembleias()}catch(err){Util.toast(err.message,true)}}} +async function loadAssembleias(){const d=await Api.get(`/api/condominios/${id}/assembleias`);const destContatos=contatos.map(c=>``).join('');const destFuncionarios=funcionariosVinculados.map(f=>``).join('');const dest=[destContatos,destFuncionarios].filter(Boolean).join('')||'

Nenhum membro cadastrado.

';document.querySelector('#assembleias').innerHTML=`

Assembleias

${dest}
${(d.assembleias||[]).map(a=>``).join('')}
TipoDataLocalStatus
${a.tipo}${Util.date(a.data_hora)}${a.local||''}${Util.badge(a.status)}
`;document.querySelector('#ass-form').onsubmit=async e=>{e.preventDefault();const f=e.target,b=Util.formData(f);b.destinatarios=[...f.querySelectorAll('input[name=dest]:checked')].map(x=>{const [tipo,did]=x.value.split(':');return{tipo_destinatario:tipo,id:Number(did)}});try{await Api.post(`/api/condominios/${id}/assembleias`,b);Util.toast('Assembleia salva');loadAssembleias();load()}catch(err){Util.toast(err.message,true)}}} async function loadFuncionariosVinculados(){const d=await Api.get(`/api/condominios/${id}/funcionarios-vinculados`);funcionariosVinculados=d.funcionarios||[]} async function loadExtras(){const d=await Api.get(`/api/extras`);extras=(d.extras||[]).filter(x=>x.status==='ativo')} async function loadDocs(){const d=await Api.get(`/api/condominios/${id}/documentos`);docs=d.documentos||[];document.querySelector('#documentos').innerHTML=`

Documentos

${docs.map(d=>``).join('')}
IDArquivoTipoDownload
${d.id}${d.nome_arquivo}${d.tipo||''}Baixar
`;document.querySelector('#doc-form').onsubmit=async e=>{e.preventDefault();const fd=new FormData(e.target);try{await Api.post(`/api/condominios/${id}/documentos`,fd);Util.toast('Documento enviado');loadDocs()}catch(err){Util.toast(err.message,true)}}} async function loadAvisos(){const d=await Api.get(`/api/condominios/${id}/avisos`);avisos=d.avisos||[];const destContatos=contatos.map(c=>``).join('');const destFuncionarios=funcionariosVinculados.map(f=>``).join('');const destExtras=extras.map(x=>``).join('');const dest=[destContatos,destFuncionarios,destExtras].filter(Boolean).join('');const docChecks=docs.map(d=>``).join('');document.querySelector('#avisos').innerHTML=`

Avisos

Nenhuma pessoa selecionada
${docChecks||'

Nenhum documento.

'}
${avisos.map(a=>``).join('')}
TituloStatusEnvioPublicado emRecorrenciaAcoes
${a.titulo}${Util.badge(a.status_envio)}${Util.date(a.data_evento)}${Util.date(a.data_publicacao)}${recLabel(a.recorrencia_meses)}
`;updateAvisoNotifSummary();document.querySelector('#aviso-form').onsubmit=async e=>{e.preventDefault();const f=e.target,b=Util.formData(f);b.enviar_whatsapp=!!f.enviar_whatsapp.checked;b.enviar_email=!!f.enviar_email.checked;b.destinatarios=[...f.querySelectorAll('input[name=dest]:checked')].map(x=>{const [tipo,did]=x.value.split(':');return{tipo_destinatario:tipo,id:Number(did)}});b.documentos=[...f.querySelectorAll('input[name=doc]:checked')].map(x=>Number(x.value));try{await Api.post(`/api/condominios/${id}/avisos`,b);Util.toast('Aviso criado');loadAvisos();load()}catch(err){Util.toast(err.message,true)}}} window.reenviarAviso=async avisoId=>{try{await Api.post(`/api/condominios/${id}/avisos/${avisoId}/reenviar`,{});Util.toast('Aviso reenviado');loadAvisos()}catch(err){Util.toast(err.message,true)}}; window.excluirAviso=async avisoId=>{try{await Api.request(`/api/condominios/${id}/avisos/${avisoId}`,{method:'DELETE'});Util.toast('Aviso excluido');loadAvisos();load()}catch(err){Util.toast(err.message,true)}}; -load(); \ No newline at end of file +load(); diff --git a/src/main/webapp/css/app.css b/src/main/webapp/css/app.css index 434e2e9..5306daf 100644 --- a/src/main/webapp/css/app.css +++ b/src/main/webapp/css/app.css @@ -3,4 +3,6 @@ .success-message{display:inline-block;margin:0 0 10px;padding:10px 12px;border:1px solid #bbf7d0;border-radius:8px;background:#dcfce7;color:var(--ok);font-weight:700} .section-head{display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:14px}.section-head h2{margin:0}.icon-btn{position:relative;width:42px;height:42px;border:1px solid var(--line);border-radius:6px;background:#fff;cursor:pointer;display:inline-grid;place-items:center}.icon-btn:hover{border-color:var(--brand);background:#eef8fb}.calendar-open::before{content:"";width:20px;height:18px;border:2px solid var(--brand);border-radius:3px;box-shadow:inset 0 5px 0 #e8f3f7;position:relative}.calendar-open::after{content:"";position:absolute;width:12px;height:2px;background:var(--brand);transform:translateY(-7px)}.calendar-modal{width:min(1120px,100%)}.calendar-layout{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:18px}.calendar-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}.calendar-toolbar strong{text-transform:capitalize}.calendar-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#fff}.calendar-weekday{padding:10px;background:#f8fafc;color:var(--muted);font-size:12px;font-weight:700;text-align:center;border-bottom:1px solid var(--line)}.calendar-day{min-height:86px;border:0;border-right:1px solid var(--line);border-bottom:1px solid var(--line);background:#fff;padding:8px;text-align:left;color:var(--text);cursor:pointer;position:relative}.calendar-day:nth-child(7n+7){border-right:0}.calendar-day.other{background:#f8fafc;color:#98a2b3}.calendar-day.busy{background:#fff7ed}.calendar-day.busy span{font-weight:700;color:var(--warn)}.calendar-day.today{box-shadow:inset 0 0 0 2px var(--brand)}.calendar-day.selected{background:#e8f3f7}.calendar-day b{position:absolute;right:8px;bottom:8px;background:var(--brand);color:#fff;border-radius:999px;padding:2px 7px;font-size:12px}.calendar-side{border:1px solid var(--line);border-radius:8px;padding:14px;background:#fff;min-height:420px}.calendar-side h3{margin:0 0 12px}.calendar-event{border:1px solid var(--line);border-radius:8px;padding:10px;margin-bottom:10px;display:grid;gap:6px}.calendar-event span{font-size:13px}.calendar-event p{margin:0;color:var(--muted);font-size:13px}@media(max-width:900px){.calendar-layout{grid-template-columns:1fr}.calendar-day{min-height:64px}.calendar-side{min-height:auto}} +.check-list{display:grid;gap:8px;max-height:150px;overflow:auto;border:1px solid var(--line);border-radius:6px;padding:10px;background:#fff}.check-list label{display:flex;align-items:center;gap:8px;color:var(--text)}.check-list input{width:auto} + @media(max-width:520px){.content{padding:12px}h1{font-size:22px}h2{font-size:17px}.panel{padding:12px;border-radius:6px}.login-page{padding:16px}.login-box{padding:20px}.modal{padding:8px;align-items:start}.modal-panel{max-height:calc(100vh - 16px);padding:14px}.modal-head{align-items:flex-start}.modal-head .btn{width:auto}.calendar-grid{font-size:12px}.calendar-weekday{padding:8px 4px}.calendar-day{min-height:54px;padding:5px}.calendar-day b{right:4px;bottom:4px;padding:1px 5px}.calendar-toolbar .btn{width:auto}.section-head{align-items:flex-start}.icon-btn{width:40px;min-width:40px}.cond-item{display:grid;grid-template-columns:1fr;align-items:stretch}.cond-item .btn{width:100%}}