feat(assembleias): adicionar responsavel com filtro dinamico por dia da semana
This commit is contained in:
@@ -82,7 +82,7 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
if(p.length==4&&"condominios".equals(p[0])&&"funcionarios-vinculados".equals(p[2])&&"DELETE".equals(method)){ if(!adminEmpresa(cargo)){HttpResponses.json(ex,403,Map.of("erro","acesso_restrito","mensagem","Apenas a conta principal pode desvincular funcionarios de condominios."));return;} execWrite(ex,c,"DELETE FROM funcionario_condominio WHERE condominio_id=? AND funcionario_id=?",Arrays.asList(id(p[1]),id(p[3])),funcionarioId,"funcionario_condominio",id(p[3])); return; }
|
||||
if(p.length==3&&"condominios".equals(p[0])&&"espacos-comuns".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)) criarEspaco(ex,c,cond,funcionarioId); else if("GET".equals(method)) listarParam(ex,c,"SELECT * FROM espacos_comuns WHERE condominio_id=? ORDER BY nome",cond,"espacos"); else HttpResponses.methodNotAllowed(ex); return; }
|
||||
if(p.length==4&&"condominios".equals(p[0])&&"espacos-comuns".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("PUT".equals(method)) atualizarEspaco(ex,c,cond,id(p[3]),funcionarioId); else if("DELETE".equals(method)) excluirEspaco(ex,c,cond,id(p[3]),funcionarioId); else HttpResponses.methodNotAllowed(ex); 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==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 a.*, r.nome responsavel_nome FROM assembleias a LEFT JOIN assembleia_responsaveis r ON r.id=a.responsavel_id WHERE a.condominio_id=? ORDER BY a.data_hora DESC",cond,"assembleias"); else HttpResponses.methodNotAllowed(ex); return; }
|
||||
if(p.length==4&&"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("PUT".equals(method)) atualizarAssembleia(ex,c,empresaId,cond,id(p[3]),funcionarioId); else if("DELETE".equals(method)) excluirAssembleia(ex,c,empresaId,cond,id(p[3]),funcionarioId); else HttpResponses.methodNotAllowed(ex); 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; }
|
||||
@@ -121,15 +121,16 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
Map<String,String>b=SimpleJson.parseObject(removeArrayField(raw,"destinatarios"));
|
||||
List<Destinatario>dests=destinatarios(raw);
|
||||
Timestamp dataHora=ts(req(b,"data_hora"));
|
||||
Map<String,Object> conflito=validarAgendaResponsaveisAssembleia(c,dataHora,null);
|
||||
Long responsavelId=optLong(b.get("responsavel_id"));
|
||||
Map<String,Object> conflito=validarAgendaResponsaveisAssembleia(c,dataHora,null,responsavelId);
|
||||
if(conflito!=null){HttpResponses.json(ex,409,conflito);return;}
|
||||
long id;
|
||||
Map<String,Object> 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"),dataHora,nullable(b.get("local")),nullable(b.get("pauta"))));
|
||||
id=insert(c,"INSERT INTO assembleias (condominio_id,tipo,data_hora,local,pauta,responsavel_id) VALUES (?,?,?,?,?,?)",Arrays.asList(cond,req(b,"tipo"),dataHora,nullable(b.get("local")),nullable(b.get("pauta")),responsavelId));
|
||||
audit(c,fid,"create","assembleias",id,null,b);
|
||||
envio=criarAvisosAssembleia(c,emp,cond,id,fid,b,dests);
|
||||
envio=criarAvisosAssembleia(c,emp,cond,id,fid,b,dests,responsavelId);
|
||||
c.commit();
|
||||
}catch(Exception err){
|
||||
c.rollback();
|
||||
@@ -137,10 +138,10 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
}finally{
|
||||
c.setAutoCommit(true);
|
||||
}
|
||||
HttpResponses.json(ex,201,Map.of("assembleia",one(c,"SELECT * FROM assembleias WHERE id=?",Arrays.asList(id)),"envio",envio));
|
||||
HttpResponses.json(ex,201,Map.of("assembleia",one(c,"SELECT a.*, r.nome responsavel_nome FROM assembleias a LEFT JOIN assembleia_responsaveis r ON r.id=a.responsavel_id WHERE a.id=?",Arrays.asList(id)),"envio",envio));
|
||||
}
|
||||
private Map<String,Object> criarAvisosAssembleia(Connection c,long emp,long cond,long assembleia,long fid,Map<String,String>b,List<Destinatario>dests)throws Exception{
|
||||
List<Destinatario> destinatarios=dests.isEmpty()?destinatariosPadraoAssembleia(c,emp,cond):dests;
|
||||
private Map<String,Object> criarAvisosAssembleia(Connection c,long emp,long cond,long assembleia,long fid,Map<String,String>b,List<Destinatario>dests,Long responsavelId)throws Exception{
|
||||
List<Destinatario> destinatarios=dests.isEmpty()?destinatariosPadraoAssembleia(c,emp,cond,responsavelId):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.");
|
||||
@@ -152,7 +153,7 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
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<Destinatario> destinatariosPadraoAssembleia(Connection c,long emp,long cond)throws SQLException{
|
||||
private List<Destinatario> destinatariosPadraoAssembleia(Connection c,long emp,long cond,Long responsavelId)throws SQLException{
|
||||
List<Destinatario> out=new ArrayList<>();
|
||||
Set<String> vistos=new HashSet<>();
|
||||
for(Map<String,Object> row:rows(c,"SELECT contato_id FROM contato_condominio WHERE condominio_id=? AND status=? AND tipo IN ('sindico','subsindico','conselheiro')",Arrays.asList(cond,"ativo")))addDestinatario(out,vistos,"contato",((Number)row.get("contato_id")).longValue());
|
||||
@@ -162,7 +163,11 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
}
|
||||
for(Map<String,Object> row:rows(c,"SELECT funcionario_id FROM funcionario_condominio WHERE condominio_id=?",Arrays.asList(cond)))addDestinatario(out,vistos,"funcionario",((Number)row.get("funcionario_id")).longValue());
|
||||
for(Map<String,Object> row:rows(c,"SELECT id FROM destinatarios_extra WHERE status='ativo'",Arrays.asList()))addDestinatario(out,vistos,"extra",((Number)row.get("id")).longValue());
|
||||
for(Map<String,Object> row:rows(c,"SELECT id FROM assembleia_responsaveis WHERE status='ativo'",Arrays.asList()))addDestinatario(out,vistos,"assembleia",((Number)row.get("id")).longValue());
|
||||
if(responsavelId!=null){
|
||||
addDestinatario(out,vistos,"assembleia",responsavelId);
|
||||
} else {
|
||||
for(Map<String,Object> row:rows(c,"SELECT id FROM assembleia_responsaveis WHERE status='ativo'",Arrays.asList()))addDestinatario(out,vistos,"assembleia",((Number)row.get("id")).longValue());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
private void addDestinatario(List<Destinatario> out,Set<String> vistos,String tipo,long id){if(vistos.add(tipo+":"+id))out.add(new Destinatario(tipo,id));}
|
||||
@@ -182,7 +187,7 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
if(b.get("pauta")!=null&&!b.get("pauta").isBlank())mensagem.append("\nPauta: ").append(b.get("pauta"));
|
||||
return mensagem.toString();
|
||||
}
|
||||
private void atualizarAssembleia(HttpExchange ex,Connection c,long emp,long cond,long aid,long fid)throws Exception{Map<String,String>b=body(ex); Map<String,Object> 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;} Timestamp novaData=b.get("data_hora")==null?null:ts(b.get("data_hora")); if(novaData!=null){Map<String,Object> conflito=validarAgendaResponsaveisAssembleia(c,novaData,aid); if(conflito!=null){HttpResponses.json(ex,409,conflito);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(novaData==null)ps.setNull(2,Types.TIMESTAMP);else ps.setTimestamp(2,novaData); 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,emp,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 atualizarAssembleia(HttpExchange ex,Connection c,long emp,long cond,long aid,long fid)throws Exception{Map<String,String>b=body(ex); Map<String,Object> 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;} Timestamp novaData=b.get("data_hora")==null?null:ts(b.get("data_hora")); Long responsavelId=b.containsKey("responsavel_id")?optLong(b.get("responsavel_id")):(old.get("responsavel_id")!=null?((Number)old.get("responsavel_id")).longValue():null); if(novaData!=null){Map<String,Object> conflito=validarAgendaResponsaveisAssembleia(c,novaData,aid,responsavelId); if(conflito!=null){HttpResponses.json(ex,409,conflito);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), responsavel_id=COALESCE(?,responsavel_id) WHERE id=? AND condominio_id=?")){ps.setString(1,b.get("tipo")); if(novaData==null)ps.setNull(2,Types.TIMESTAMP);else ps.setTimestamp(2,novaData); ps.setString(3,b.get("local"));ps.setString(4,b.get("pauta"));ps.setString(5,b.get("status")); if(responsavelId==null)ps.setNull(6,Types.BIGINT);else ps.setLong(6,responsavelId); ps.setLong(7,aid);ps.setLong(8,cond);ps.executeUpdate();} if("cancelada".equalsIgnoreCase(b.get("status"))&&!"cancelada".equalsIgnoreCase(str(old.get("status"))))criarAvisoCancelamentoAssembleia(c,emp,cond,aid,fid,old); audit(c,fid,"update","assembleias",aid,old,b); c.commit();HttpResponses.json(ex,200,Map.of("assembleia",one(c,"SELECT a.*, r.nome responsavel_nome FROM assembleias a LEFT JOIN assembleia_responsaveis r ON r.id=a.responsavel_id WHERE a.id=?",Arrays.asList(aid))));}catch(Exception err){c.rollback();throw err;}finally{c.setAutoCommit(true);}}
|
||||
private void criarAvisoCancelamentoAssembleia(Connection c,long emp,long cond,long aid,long fid,Map<String,Object>old)throws SQLException{
|
||||
List<Map<String,Object>> avisos=rows(c,"SELECT id FROM avisos WHERE assembleia_id=?",Arrays.asList(aid));
|
||||
Set<String> vistos=new HashSet<>();
|
||||
@@ -194,9 +199,10 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
}
|
||||
}
|
||||
if(dests.isEmpty()){
|
||||
Map<String,Object> a=one(c,"SELECT condominio_id FROM assembleias WHERE id=?",Arrays.asList(aid));
|
||||
Map<String,Object> a=one(c,"SELECT condominio_id, responsavel_id FROM assembleias WHERE id=?",Arrays.asList(aid));
|
||||
long cid=a!=null&&a.get("condominio_id")!=null?((Number)a.get("condominio_id")).longValue():cond;
|
||||
dests=destinatariosPadraoAssembleia(c,emp,cid);
|
||||
Long respId=a!=null&&a.get("responsavel_id")!=null?((Number)a.get("responsavel_id")).longValue():null;
|
||||
dests=destinatariosPadraoAssembleia(c,emp,cid,respId);
|
||||
}
|
||||
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();}
|
||||
@@ -218,7 +224,8 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
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<Destinatario> padrao=destinatariosPadraoAssembleia(c,emp,cond);
|
||||
Long respId=ass.get("responsavel_id")!=null?((Number)ass.get("responsavel_id")).longValue():null;
|
||||
List<Destinatario> padrao=destinatariosPadraoAssembleia(c,emp,cond,respId);
|
||||
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;
|
||||
@@ -304,23 +311,20 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
private void criarResponsavelAssembleia(HttpExchange ex,Connection c,long fid)throws Exception{String raw=rawBody(ex);Map<String,String>b=SimpleJson.parseObject(removeArrayField(raw,"dias_semana"));String dias=diasSemana(raw,b.get("dias_semana"));long id=insert(c,"INSERT INTO assembleia_responsaveis (nome,telefone,email,dias_semana,status) VALUES (?,?,?,?,'ativo')",Arrays.asList(req(b,"nome"),nullable(b.get("telefone")),nullable(b.get("email")),dias));audit(c,fid,"create","assembleia_responsaveis",id,null,b);HttpResponses.json(ex,201,Map.of("responsavel",one(c,"SELECT * FROM assembleia_responsaveis WHERE id=?",Arrays.asList(id))));}
|
||||
private void atualizarResponsavelAssembleia(HttpExchange ex,Connection c,long id,long fid)throws Exception{Map<String,Object> old=one(c,"SELECT * FROM assembleia_responsaveis WHERE id=?",Arrays.asList(id));if(old==null){HttpResponses.json(ex,404,Map.of("erro","responsavel_nao_encontrado"));return;}String raw=rawBody(ex);Map<String,String>b=SimpleJson.parseObject(removeArrayField(raw,"dias_semana"));String dias=raw.contains("\"dias_semana\"")?diasSemana(raw,b.get("dias_semana")):null;try(PreparedStatement ps=c.prepareStatement("UPDATE assembleia_responsaveis SET nome=COALESCE(?,nome), telefone=COALESCE(?,telefone), email=COALESCE(?,email), dias_semana=COALESCE(?,dias_semana), status=COALESCE(?,status), atualizado_em=now() WHERE id=?")){ps.setString(1,b.get("nome"));ps.setString(2,b.get("telefone"));ps.setString(3,b.get("email"));ps.setString(4,dias);ps.setString(5,b.get("status"));ps.setLong(6,id);ps.executeUpdate();}audit(c,fid,"update","assembleia_responsaveis",id,old,b);HttpResponses.json(ex,200,Map.of("responsavel",one(c,"SELECT * FROM assembleia_responsaveis WHERE id=?",Arrays.asList(id))));}
|
||||
private void excluirResponsavelAssembleia(HttpExchange ex,Connection c,long id,long fid)throws Exception{Map<String,Object> old=one(c,"SELECT * FROM assembleia_responsaveis WHERE id=?",Arrays.asList(id));if(old==null){HttpResponses.json(ex,404,Map.of("erro","responsavel_nao_encontrado"));return;}try(PreparedStatement ps=c.prepareStatement("UPDATE assembleia_responsaveis SET status='excluido', atualizado_em=now() WHERE id=?")){ps.setLong(1,id);ps.executeUpdate();}audit(c,fid,"delete","assembleia_responsaveis",id,old,Map.of("ok",true));HttpResponses.json(ex,200,Map.of("ok",true));}
|
||||
private Map<String,Object> validarAgendaResponsaveisAssembleia(Connection c,Timestamp dataHora,Long ignorarAssembleia)throws Exception{
|
||||
if(dataHora==null)return null;
|
||||
List<Map<String,Object>> responsaveis=rows(c,"SELECT id,nome,dias_semana FROM assembleia_responsaveis WHERE status='ativo' ORDER BY nome",Arrays.asList());
|
||||
if(responsaveis.isEmpty())return null;
|
||||
private Map<String,Object> validarAgendaResponsaveisAssembleia(Connection c,Timestamp dataHora,Long ignorarAssembleia,Long responsavelId)throws Exception{
|
||||
if(dataHora==null||responsavelId==null)return null;
|
||||
Map<String,Object> r=one(c,"SELECT id,nome,dias_semana FROM assembleia_responsaveis WHERE id=? AND status='ativo'",Arrays.asList(responsavelId));
|
||||
if(r==null)return Map.of("erro","responsavel_invalido","mensagem","Responsável selecionado não encontrado ou inativo.");
|
||||
int dia=dataHora.toLocalDateTime().getDayOfWeek().getValue()%7;
|
||||
List<String> indisponiveis=new ArrayList<>();
|
||||
for(Map<String,Object> r:responsaveis){
|
||||
Set<String> dias=new HashSet<>(Arrays.asList(str(r.get("dias_semana")).split(",")));
|
||||
if(!dias.contains(String.valueOf(dia)))indisponiveis.add(str(r.get("nome")));
|
||||
Set<String> dias=new HashSet<>(Arrays.asList(str(r.get("dias_semana")).split(",")));
|
||||
if(!dias.contains(String.valueOf(dia))){
|
||||
return Map.of("erro","responsavel_indisponivel","mensagem","O responsável "+r.get("nome")+" não está disponível para este dia da semana.");
|
||||
}
|
||||
if(!indisponiveis.isEmpty())return Map.of("erro","responsavel_indisponivel","mensagem","Nao e possivel agendar assembleia neste dia da semana para: "+String.join(", ",indisponiveis)+".","responsaveis",indisponiveis);
|
||||
String sql="SELECT a.id,a.data_hora,r.nome FROM assembleias a CROSS JOIN assembleia_responsaveis r WHERE r.status='ativo' AND a.status<>'cancelada' AND a.data_hora::date=?::date"+(ignorarAssembleia==null?"":" AND a.id<>?");
|
||||
List<Object> params=new ArrayList<>();params.add(dataHora);if(ignorarAssembleia!=null)params.add(ignorarAssembleia);
|
||||
String sql="SELECT a.id,a.data_hora FROM assembleias a WHERE a.responsavel_id=? AND a.status<>'cancelada' AND a.data_hora::date=?::date"+(ignorarAssembleia==null?"":" AND a.id<>?");
|
||||
List<Object> params=new ArrayList<>();params.add(responsavelId);params.add(dataHora);if(ignorarAssembleia!=null)params.add(ignorarAssembleia);
|
||||
List<Map<String,Object>> conflitos=rows(c,sql,params);
|
||||
if(!conflitos.isEmpty()){
|
||||
List<String> nomes=conflitos.stream().map(x->str(x.get("nome"))).distinct().toList();
|
||||
return Map.of("erro","assembleia_duplicada_responsavel","mensagem","Ja existe assembleia agendada neste dia para: "+String.join(", ",nomes)+".","responsaveis",nomes);
|
||||
return Map.of("erro","assembleia_duplicada_responsavel","mensagem","O responsável "+r.get("nome")+" já possui uma assembleia agendada para esta data.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -463,7 +467,7 @@ public final class ClienteHandlers implements HttpHandler {
|
||||
private Map<String,Object> evolutionInstancia(long empresaId)throws Exception{try(Connection m=masterDs.getMasterConnection();PreparedStatement ps=m.prepareStatement("SELECT id,empresa_id,instance_url,instance_token_enc,numero_whatsapp,status,solicitado_em,aprovado_em FROM evolution_instancias WHERE empresa_id=? ORDER BY solicitado_em DESC,id DESC LIMIT 1")){ps.setLong(1,empresaId);try(ResultSet rs=ps.executeQuery()){return rs.next()?resultMap(rs):null;}}}
|
||||
|
||||
private void listarAssembleiasCalendario(HttpExchange ex,Connection c,long emp,String cargo,long fid)throws Exception{
|
||||
List<Map<String,Object>> out=rows(c,"SELECT a.*, co.nome condominio_nome FROM assembleias a JOIN condominios co ON co.id=a.condominio_id ORDER BY a.data_hora DESC",Arrays.asList());
|
||||
List<Map<String,Object>> out=rows(c,"SELECT a.*, co.nome condominio_nome, r.nome responsavel_nome FROM assembleias a JOIN condominios co ON co.id=a.condominio_id LEFT JOIN assembleia_responsaveis r ON r.id=a.responsavel_id ORDER BY a.data_hora DESC",Arrays.asList());
|
||||
for(Map<String,Object>a:out){
|
||||
long aid=((Number)a.get("id")).longValue();
|
||||
Map<String,Object> aviso=one(c,"SELECT id FROM avisos WHERE assembleia_id=? ORDER BY id DESC LIMIT 1",Arrays.asList(aid));
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE assembleias ADD COLUMN IF NOT EXISTS responsavel_id BIGINT REFERENCES assembleia_responsaveis(id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assembleias_responsavel ON assembleias(responsavel_id);
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Assembleia</title><link rel="stylesheet" href="/css/app.css"><script src="/js/auth.js"></script><script src="/js/util.js"></script><script src="/js/api.js"></script><script src="/js/layout.js"></script></head><body><main><div class="topbar"><h1>Assembleia</h1></div><section class="panel"><div class="section-head"><h2>Todas as assembleias</h2><div class="section-actions"><button class="search-icon-btn" type="button" onclick="alternarBuscaAssembleias()" aria-label="Buscar assembleias" title="Buscar"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m16.5 16.5 4 4"></path></svg></button><button class="icon-btn calendar-open" type="button" onclick="abrirCalendarioAssembleias()" title="Calendário de assembleias" aria-label="Calendário de assembleias"></button></div></div><div id="assembleia-search" class="search-strip hidden"><input id="assembleia-search-input" placeholder="Buscar por condomínio, tipo, data, local ou status"></div><div class="table-wrap"><table><thead><tr><th>Condomínio</th><th>Tipo</th><th>Data</th><th>Local</th><th>Status</th><th>Notificados</th><th style="text-align:right">Ações</th></tr></thead><tbody id="rows"></tbody></table></div></section><section class="panel"><div class="section-head"><h2>Responsáveis pela assembleia</h2><div class="section-actions"><button class="header-add-btn" type="button" onclick="abrirNovoResponsavel()" title="Novo responsável" aria-label="Novo responsável"><svg viewBox="0 0 24 24" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg></button></div></div><div class="table-wrap"><table><thead><tr><th>Nome</th><th>WhatsApp</th><th>E-mail</th><th>Dias disponíveis</th><th>Status</th><th style="text-align:right">Ações</th></tr></thead><tbody id="responsaveis-rows"></tbody></table></div></section><div id="responsavel-modal" class="modal hidden"><div class="modal-backdrop" onclick="fecharResponsavelModal()"></div><section class="modal-panel"><div class="modal-head"><h2 id="responsavel-modal-title">Responsável</h2><button class="btn secondary" type="button" onclick="fecharResponsavelModal()">Fechar</button></div><div id="responsavel-modal-body"></div></section></div></main><script>Layout.mount('empresa','assembleias');</script><script src="/cliente/assembleias.js"></script></body></html>
|
||||
<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Assembleia</title><link rel="stylesheet" href="/css/app.css"><script src="/js/auth.js"></script><script src="/js/util.js"></script><script src="/js/api.js"></script><script src="/js/layout.js"></script></head><body><main><div class="topbar"><h1>Assembleia</h1></div><section class="panel"><div class="section-head"><h2>Todas as assembleias</h2><div class="section-actions"><button class="search-icon-btn" type="button" onclick="alternarBuscaAssembleias()" aria-label="Buscar assembleias" title="Buscar"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m16.5 16.5 4 4"></path></svg></button><button class="icon-btn calendar-open" type="button" onclick="abrirCalendarioAssembleias()" title="Calendário de assembleias" aria-label="Calendário de assembleias"></button></div></div><div id="assembleia-search" class="search-strip hidden"><input id="assembleia-search-input" placeholder="Buscar por condomínio, tipo, data, local ou status"></div><div class="table-wrap"><table><thead><tr><th>Condomínio</th><th>Tipo</th><th>Data</th><th>Local</th><th>Responsável</th><th>Status</th><th>Notificados</th><th style="text-align:right">Ações</th></tr></thead><tbody id="rows"></tbody></table></div></section><section class="panel"><div class="section-head"><h2>Responsáveis pela assembleia</h2><div class="section-actions"><button class="header-add-btn" type="button" onclick="abrirNovoResponsavel()" title="Novo responsável" aria-label="Novo responsável"><svg viewBox="0 0 24 24" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg></button></div></div><div class="table-wrap"><table><thead><tr><th>Nome</th><th>WhatsApp</th><th>E-mail</th><th>Dias disponíveis</th><th>Status</th><th style="text-align:right">Ações</th></tr></thead><tbody id="responsaveis-rows"></tbody></table></div></section><div id="responsavel-modal" class="modal hidden"><div class="modal-backdrop" onclick="fecharResponsavelModal()"></div><section class="modal-panel"><div class="modal-head"><h2 id="responsavel-modal-title">Responsável</h2><button class="btn secondary" type="button" onclick="fecharResponsavelModal()">Fechar</button></div><div id="responsavel-modal-body"></div></section></div></main><script>Layout.mount('empresa','assembleias');</script><script src="/cliente/assembleias.js"></script></body></html>
|
||||
|
||||
@@ -23,8 +23,8 @@ const Icons = {
|
||||
|
||||
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=>`<tr><td><strong>${esc(a.condominio_nome||'-')}</strong></td><td>${esc(a.tipo)}</td><td>${Util.date(a.data_hora)}</td><td>${esc(a.local||'')}</td><td>${Util.badge(a.status)}</td><td>${esc(destinatariosLabel(a))}</td><td style="text-align:right"><div class="table-actions"><button class="table-icon-btn send" type="button" onclick="enviarNotificacaoAssembleia(this,${a.condominio_id},${a.id})" title="Enviar notificação" aria-label="Enviar notificação">${Icons.send}</button><button class="table-icon-btn danger" type="button" onclick="excluirAssembleia(${a.condominio_id},${a.id})" title="Excluir assembleia" aria-label="Excluir assembleia">${Icons.trash}</button></div></td></tr>`).join('')||'<tr><td colspan="7">Nenhuma assembleia encontrada.</td></tr>';
|
||||
const lista=assembleias.filter(a=>contem(q,a.condominio_nome,a.tipo,a.data_hora,a.local,a.responsavel_nome,a.status,destinatariosLabel(a)));
|
||||
rows.innerHTML=lista.map(a=>`<tr><td><strong>${esc(a.condominio_nome||'-')}</strong></td><td>${esc(a.tipo)}</td><td>${Util.date(a.data_hora)}</td><td>${esc(a.local||'')}</td><td>${esc(a.responsavel_nome||'-')}</td><td>${Util.badge(a.status)}</td><td>${esc(destinatariosLabel(a))}</td><td style="text-align:right"><div class="table-actions"><button class="table-icon-btn send" type="button" onclick="enviarNotificacaoAssembleia(this,${a.condominio_id},${a.id})" title="Enviar notificação" aria-label="Enviar notificação">${Icons.send}</button><button class="table-icon-btn danger" type="button" onclick="excluirAssembleia(${a.condominio_id},${a.id})" title="Excluir assembleia" aria-label="Excluir assembleia">${Icons.trash}</button></div></td></tr>`).join('')||'<tr><td colspan="8">Nenhuma assembleia encontrada.</td></tr>';
|
||||
}
|
||||
function ensureCalendarModal(){let m=document.querySelector('#assembleia-calendar-modal');if(m)return m;document.body.insertAdjacentHTML('beforeend','<div id="assembleia-calendar-modal" class="modal hidden"><div class="modal-backdrop" onclick="fecharCalendarioAssembleias()"></div><section class="modal-panel calendar-modal"><div class="modal-head"><h2>Calendário de assembleias</h2><button class="btn secondary" type="button" onclick="fecharCalendarioAssembleias()">Fechar</button></div><div class="calendar-layout"><div><div class="calendar-toolbar"><button class="btn secondary" type="button" onclick="mudarMesCalendario(-1)">Anterior</button><strong id="calendar-title"></strong><button class="btn secondary" type="button" onclick="mudarMesCalendario(1)">Próximo</button></div><div id="calendar-grid" class="calendar-grid"></div></div><aside class="calendar-side"><h3 id="calendar-day-title">Selecione um dia</h3><div id="calendar-day-list" class="calendar-day-list"><p class="muted">Clique em um dia para ver as assembleias.</p></div></aside></div></section></div>');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=>'<div class="calendar-weekday">'+x+'</div>').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+='<button type="button" class="'+classes.join(' ')+'" data-date="'+key+'"><span>'+d.getDate()+'</span>'+(items.length?'<b>'+items.length+'</b>':'')+'</button>'}grid.innerHTML=html;grid.querySelectorAll('.calendar-day').forEach(btn=>btn.onclick=()=>selecionarDiaCalendario(btn.dataset.date));if(selected)selecionarDiaCalendario(selected,false)}
|
||||
@@ -77,7 +77,7 @@ 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='<p class="muted">Nenhuma assembleia agendada neste dia.</p>';return;}list.innerHTML=items.map(a=>'<div class="calendar-event"><strong>'+esc(a.condominio_nome||'-')+'</strong><span>'+Util.date(a.data_hora)+'</span><span>'+esc(a.tipo)+' - '+esc(a.local||'-')+'</span><p>'+esc(destinatariosLabel(a))+'</p><div class="table-actions start"><button class="table-icon-btn send" type="button" onclick="enviarNotificacaoAssembleia(this,'+a.condominio_id+','+a.id+')" title="Enviar notificação" aria-label="Enviar notificação">'+Icons.send+'</button><button class="table-icon-btn danger" type="button" onclick="excluirAssembleia('+a.condominio_id+','+a.id+')" title="Excluir assembleia" aria-label="Excluir assembleia">'+Icons.trash+'</button></div></div>').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='<p class="muted">Nenhuma assembleia agendada neste dia.</p>';return;}list.innerHTML=items.map(a=>'<div class="calendar-event"><strong>'+esc(a.condominio_nome||'-')+'</strong><span>'+Util.date(a.data_hora)+'</span><span>'+esc(a.tipo)+' - '+esc(a.local||'-')+'</span><span>Responsável: <strong>'+esc(a.responsavel_nome||'-')+'</strong></span><p>'+esc(destinatariosLabel(a))+'</p><div class="table-actions start"><button class="table-icon-btn send" type="button" onclick="enviarNotificacaoAssembleia(this,'+a.condominio_id+','+a.id+')" title="Enviar notificação" aria-label="Enviar notificação">'+Icons.send+'</button><button class="table-icon-btn danger" type="button" onclick="excluirAssembleia('+a.condominio_id+','+a.id+')" title="Excluir assembleia" aria-label="Excluir assembleia">'+Icons.trash+'</button></div></div>').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.innerHTML:'';if(btn){btn.disabled=true;btn.innerHTML='...';}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.innerHTML=original;}}};
|
||||
window.excluirAssembleia=async(condominioId,assembleiaId)=>{if(!await Util.confirm('Tem certeza que deseja excluir esta assembleia? Esta ação não pode ser desfeita.','Excluir assembleia'))return;try{const res=await Api.request(`/api/condominios/${condominioId}/assembleias/${assembleiaId}`,{method:'DELETE'});Util.toast(res?.mensagem||'Assembleia excluída com sucesso.');await load();if(document.querySelector('#assembleia-calendar-modal')&&!document.querySelector('#assembleia-calendar-modal').classList.contains('hidden')){renderCalendarioAssembleias();}}catch(err){Util.toast(err.message,true);}};
|
||||
window.abrirNovoResponsavel=()=>abrirResponsavel({});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const id=new URLSearchParams(location.search).get('id');const sections=['contatos','espacos','assembleias','documentos','avisos','respostas'];document.querySelectorAll('.nav-tabs button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.nav-tabs button').forEach(x=>x.classList.remove('active'));b.classList.add('active');sections.forEach(s=>document.querySelector('#'+s).classList.toggle('hidden',s!==b.dataset.tab))});let contatos=[],docs=[],avisos=[],respostas=[],funcionariosVinculados=[],extras=[],assembleiasCalendario=[],calMonth=new Date(),espacos=[],assembleias=[];function recLabel(v){return {1:'Mensal',3:'Trimestral',12:'Anual'}[Number(v)]||'Sem recorrencia'}
|
||||
const id=new URLSearchParams(location.search).get('id');const sections=['contatos','espacos','assembleias','documentos','avisos','respostas'];document.querySelectorAll('.nav-tabs button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.nav-tabs button').forEach(x=>x.classList.remove('active'));b.classList.add('active');sections.forEach(s=>document.querySelector('#'+s).classList.toggle('hidden',s!==b.dataset.tab))});let contatos=[],docs=[],avisos=[],respostas=[],funcionariosVinculados=[],extras=[],assembleiasCalendario=[],calMonth=new Date(),espacos=[],assembleias=[],responsaveis=[];const diasNomes=['Domingo','Segunda','Terça','Quarta','Quinta','Sexta','Sábado'];function recLabel(v){return {1:'Mensal',3:'Trimestral',12:'Anual'}[Number(v)]||'Sem recorrencia'}
|
||||
const Icons = {
|
||||
add: '<svg viewBox="0 0 24 24" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>',
|
||||
search: '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m16.5 16.5 4 4"></path></svg>',
|
||||
@@ -25,7 +25,7 @@ function renderCalendarioAssembleias(selected){ensureCalendarModal();const title
|
||||
window.abrirCalendarioAssembleias=async()=>{try{const d=await Api.get('/api/assembleias-calendario');assembleiasCalendario=d.assembleias||[];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=assembleiasCalendario.filter(a=>dateKey(a.data_hora)===key);const [yy,mm,dd]=key.split('-');title.textContent=dd+'/'+mm+'/'+yy;if(!items.length){list.innerHTML='<p class="muted">Nenhuma assembleia agendada neste dia.</p>';return;}list.innerHTML=items.map(a=>{const dest=(a.destinatarios||[]).map(x=>x.nome+' ('+x.tipo+')').join(', ')||'Nenhum destinatário registrado';return '<div class="calendar-event"><strong>'+a.condominio_nome+'</strong><span>'+Util.date(a.data_hora)+'</span><span>'+a.tipo+' - '+(a.local||'-')+'</span><p>'+dest+'</p><div class="table-actions start"><button class="table-icon-btn send" type="button" onclick="reenviarAvisoAssembleia('+a.condominio_id+','+a.id+')" title="Enviar notificação" aria-label="Enviar notificação">'+Icons.send+'</button><button class="table-icon-btn danger" type="button" onclick="cancelarAssembleiaCalendario('+a.condominio_id+','+a.id+')" title="Cancelar assembleia" aria-label="Cancelar assembleia">'+Icons.trash+'</button></div></div>'}).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=assembleiasCalendario.filter(a=>dateKey(a.data_hora)===key);const [yy,mm,dd]=key.split('-');title.textContent=dd+'/'+mm+'/'+yy;if(!items.length){list.innerHTML='<p class="muted">Nenhuma assembleia agendada neste dia.</p>';return;}list.innerHTML=items.map(a=>{const dest=(a.destinatarios||[]).map(x=>x.nome+' ('+x.tipo+')').join(', ')||'Nenhum destinatário registrado';return '<div class="calendar-event"><strong>'+esc(a.condominio_nome||'-')+'</strong><span>'+Util.date(a.data_hora)+'</span><span>'+esc(a.tipo)+' - '+(esc(a.local)||'-')+'</span><span>Responsável: <strong>'+esc(a.responsavel_nome||'-')+'</strong></span><p>'+dest+'</p><div class="table-actions start"><button class="table-icon-btn send" type="button" onclick="reenviarAvisoAssembleia('+a.condominio_id+','+a.id+')" title="Enviar notificação" aria-label="Enviar notificação">'+Icons.send+'</button><button class="table-icon-btn danger" type="button" onclick="cancelarAssembleiaCalendario('+a.condominio_id+','+a.id+')" title="Cancelar assembleia" aria-label="Cancelar assembleia">'+Icons.trash+'</button></div></div>'}).join('')};
|
||||
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','<div id="aviso-notif-modal" class="modal hidden"><div class="modal-backdrop" onclick="fecharAvisoNotificacao()"></div><section class="modal-panel"><div class="modal-head"><h2>Notificação</h2><button class="btn secondary" type="button" onclick="fecharAvisoNotificacao()">Fechar</button></div><div id="aviso-notif-body"></div><div class="modal-actions"><button class="btn secondary" type="button" onclick="fecharAvisoNotificacao()">Cancelar</button><button class="btn" type="button" onclick="salvarAvisoNotificacao()">Salvar</button></div></section></div>');return document.querySelector('#aviso-notif-modal')}
|
||||
@@ -35,7 +35,7 @@ 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=`<p>${e.message}</p>`;Util.toast(e.message,true)}}
|
||||
async function load(){try{const d=await Api.get(`/api/condominios/${id}`);document.querySelector('#header').innerHTML=`<h2>${d.condominio.nome}</h2><p>${d.condominio.endereco||''}</p><div class="cards"><div class="stat">Sindico<strong>${d.sindico_atual?.contato_nome||'-'}</strong></div><div class="stat">Assembleias futuras<strong>${d.assembleias_futuras||0}</strong></div><div class="stat">Avisos ativos<strong>${d.avisos_ativos||0}</strong></div></div>`;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);await safeLoad('respostas',loadRespostas)}catch(e){Util.toast(e.message,true)}}
|
||||
async function load(){try{const d=await Api.get(`/api/condominios/${id}`);document.querySelector('#header').innerHTML=`<h2>${d.condominio.nome}</h2><p>${d.condominio.endereco||''}</p><div class="cards"><div class="stat">Sindico<strong>${d.sindico_atual?.contato_nome||'-'}</strong></div><div class="stat">Assembleias futuras<strong>${d.assembleias_futuras||0}</strong></div><div class="stat">Avisos ativos<strong>${d.avisos_ativos||0}</strong></div></div>`;await safeLoad('contatos',loadContatos);await safeLoad('avisos',loadFuncionariosVinculados);await safeLoad('avisos',loadExtras);await safeLoad('assembleias',loadResponsaveis);await safeLoad('espacos',loadEspacos);await safeLoad('assembleias',loadAssembleias);await safeLoad('documentos',loadDocs);await safeLoad('avisos',loadAvisos);await safeLoad('respostas',loadRespostas)}catch(e){Util.toast(e.message,true)}}
|
||||
async function loadContatos(){const d=await Api.get(`/api/condominios/${id}/contatos`);contatos=d.contatos||[];renderContatos()}
|
||||
function renderContatos(){
|
||||
const q=termoDetalhe('contatos');
|
||||
@@ -57,16 +57,71 @@ window.abrirEditarEspaco=eid=>{const x=espacos.find(e=>Number(e.id)===Number(eid
|
||||
window.excluirEspacoDetalhe=async eid=>{if(!await Util.confirm('Tem certeza que deseja excluir este espaço comum?','Excluir espaço'))return;try{await Api.request(`/api/condominios/${id}/espacos-comuns/${eid}`,{method:'DELETE'});Util.toast('Espaço excluído');loadEspacos()}catch(err){Util.toast(err.message,true)}};
|
||||
window.abrirNovoEspaco=()=>{abrirDetailModal('Novo espaço comum','<form id="espaco-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required></div><div class="field"><label>Valor centavos</label><input name="valor_utilizacao_centavos" type="number"></div><div class="field"><label>Índice</label><input name="indice_correcao"></div><div class="field"><label>Descrição</label><input name="descricao"></div><div class="modal-actions" style="grid-column: 1 / -1;"><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button><button class="btn" type="submit">Cadastrar espaço</button></div></form>');document.querySelector('#espaco-form').onsubmit=async e=>{e.preventDefault();try{await Api.post(`/api/condominios/${id}/espacos-comuns`,Util.formData(e.target));Util.toast('Espaço salvo');fecharDetailModal();loadEspacos()}catch(err){Util.toast(err.message,true)}}};
|
||||
|
||||
async function loadResponsaveis(){try{const d=await Api.get('/api/assembleia-responsaveis');responsaveis=(d.responsaveis||[]).filter(r=>r.status==='ativo');}catch(e){}}
|
||||
async function loadAssembleias(){const d=await Api.get(`/api/condominios/${id}/assembleias`);assembleias=d.assembleias||[];renderAssembleias()}
|
||||
function renderAssembleias(){
|
||||
const q=termoDetalhe('assembleias');
|
||||
const lista=assembleias.filter(a=>contemDetalhe(q,a.tipo,a.data_hora,a.local,a.status));
|
||||
const lista=assembleias.filter(a=>contemDetalhe(q,a.tipo,a.data_hora,a.local,a.responsavel_nome,a.status));
|
||||
const calendar='<button class="icon-btn calendar-open" type="button" onclick="abrirCalendarioAssembleias()" title="Calendário de assembleias" aria-label="Calendário de assembleias"></button>';
|
||||
document.querySelector('#assembleias').innerHTML=headerCadastro('Assembleias','assembleias','abrirNovaAssembleia()',calendar)+`<div class="table-wrap"><table><thead><tr><th>Tipo</th><th>Data</th><th>Local</th><th>Status</th><th style="text-align:right">Ações</th></tr></thead><tbody>${lista.map(a=>`<tr><td>${esc(a.tipo)}</td><td>${Util.date(a.data_hora)}</td><td>${esc(a.local||'')}</td><td>${Util.badge(a.status)}</td><td style="text-align:right"><div class="table-actions"><button class="table-icon-btn send" type="button" onclick="reenviarAvisoAssembleia(${id},${a.id})" title="Enviar notificação" aria-label="Enviar notificação">${Icons.send}</button><button class="table-icon-btn danger" type="button" onclick="excluirAssembleiaDetalhe(${a.id})" title="Excluir assembleia" aria-label="Excluir assembleia">${Icons.trash}</button></div></td></tr>`).join('')||'<tr><td colspan="5">Nenhuma assembleia encontrada.</td></tr>'}</tbody></table></div>`;
|
||||
document.querySelector('#assembleias').innerHTML=headerCadastro('Assembleias','assembleias','abrirNovaAssembleia()',calendar)+`<div class="table-wrap"><table><thead><tr><th>Tipo</th><th>Data</th><th>Local</th><th>Responsável</th><th>Status</th><th style="text-align:right">Ações</th></tr></thead><tbody>${lista.map(a=>`<tr><td>${esc(a.tipo)}</td><td>${Util.date(a.data_hora)}</td><td>${esc(a.local||'')}</td><td>${esc(a.responsavel_nome||'-')}</td><td>${Util.badge(a.status)}</td><td style="text-align:right"><div class="table-actions"><button class="table-icon-btn send" type="button" onclick="reenviarAvisoAssembleia(${id},${a.id})" title="Enviar notificação" aria-label="Enviar notificação">${Icons.send}</button><button class="table-icon-btn danger" type="button" onclick="excluirAssembleiaDetalhe(${a.id})" title="Excluir assembleia" aria-label="Excluir assembleia">${Icons.trash}</button></div></td></tr>`).join('')||'<tr><td colspan="6">Nenhuma assembleia encontrada.</td></tr>'}</tbody></table></div>`;
|
||||
document.querySelector('#assembleias-search-input')?.addEventListener('input',renderAssembleias);
|
||||
}
|
||||
window.excluirAssembleiaDetalhe=async aid=>{if(!await Util.confirm('Tem certeza que deseja excluir esta assembleia? Esta ação não pode ser desfeita.','Excluir assembleia'))return;try{await Api.request(`/api/condominios/${id}/assembleias/${aid}`,{method:'DELETE'});Util.toast('Assembleia excluída');await loadAssembleias();await load()}catch(err){Util.toast(err.message,true)}};
|
||||
window.abrirNovaAssembleia=()=>{abrirDetailModal('Nova assembleia','<form id="ass-form" class="grid cols"><div class="field"><label>Tipo</label><input name="tipo" required></div><div class="field"><label>Data e hora</label><input name="data_hora" type="datetime-local" required></div><div class="field"><label>Local</label><input name="local"></div><div class="field"><label>Pauta</label><input name="pauta"></div><div class="modal-actions" style="grid-column: 1 / -1;"><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button><button class="btn" type="submit">Cadastrar assembleia</button></div></form>');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');fecharDetailModal();loadAssembleias();load()}catch(err){Util.toast(err.message,true)}}};
|
||||
window.abrirNovaAssembleia=async ()=>{
|
||||
if(!responsaveis.length)await loadResponsaveis();
|
||||
abrirDetailModal('Nova assembleia',`<form id="ass-form" class="grid cols"><div class="field"><label>Tipo</label><input name="tipo" required></div><div class="field"><label>Data e hora</label><input name="data_hora" id="ass-data-hora" type="datetime-local" required></div><div class="field"><label>Responsável pela assembleia</label><select name="responsavel_id" id="ass-responsavel" required><option value="">Selecione a data e hora primeiro</option></select></div><div class="field"><label>Local</label><input name="local"></div><div class="field" style="grid-column: 1 / -1;"><label>Pauta</label><input name="pauta"></div><div class="modal-actions" style="grid-column: 1 / -1;"><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button><button class="btn" type="submit">Cadastrar assembleia</button></div></form>`);
|
||||
const dtInput=document.querySelector('#ass-data-hora');
|
||||
const selResp=document.querySelector('#ass-responsavel');
|
||||
function filtrarResponsaveisPorData(){
|
||||
const val=dtInput?.value;
|
||||
if(!val){
|
||||
selResp.innerHTML='<option value="">Selecione a data e hora primeiro</option>';
|
||||
return;
|
||||
}
|
||||
const datePart=val.split('T')[0];
|
||||
if(!datePart){
|
||||
selResp.innerHTML='<option value="">Selecione a data e hora primeiro</option>';
|
||||
return;
|
||||
}
|
||||
const [y,m,d]=datePart.split('-').map(Number);
|
||||
if(!y||!m||!d){
|
||||
selResp.innerHTML='<option value="">Selecione a data e hora primeiro</option>';
|
||||
return;
|
||||
}
|
||||
const dateObj=new Date(y,m-1,d);
|
||||
const dia=dateObj.getDay();
|
||||
const diaNome=diasNomes[dia]||'';
|
||||
const disp=(responsaveis||[]).filter(r=>{
|
||||
if(r.status&&r.status!=='ativo')return false;
|
||||
const dias=String(r.dias_semana||'').split(',').map(x=>x.trim());
|
||||
return dias.includes(String(dia));
|
||||
});
|
||||
if(!disp.length){
|
||||
selResp.innerHTML=`<option value="">Nenhum responsável disponível para ${diaNome}</option>`;
|
||||
}else{
|
||||
selResp.innerHTML=`<option value="">Selecione o responsável (${diaNome})</option>`+disp.map(r=>`<option value="${r.id}">${esc(r.nome)}</option>`).join('');
|
||||
}
|
||||
}
|
||||
dtInput?.addEventListener('input',filtrarResponsaveisPorData);
|
||||
dtInput?.addEventListener('change',filtrarResponsaveisPorData);
|
||||
document.querySelector('#ass-form').onsubmit=async e=>{
|
||||
e.preventDefault();
|
||||
const f=e.target;
|
||||
if(!f.responsavel_id.value){
|
||||
Util.toast('Selecione um responsável disponível para a data da assembleia.',true);
|
||||
return;
|
||||
}
|
||||
try{
|
||||
await Api.post(`/api/condominios/${id}/assembleias`,Util.formData(f));
|
||||
Util.toast('Assembleia salva');
|
||||
fecharDetailModal();
|
||||
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||[];renderDocs()}
|
||||
|
||||
Reference in New Issue
Block a user