Adiciona respostas por condominio

This commit is contained in:
Tiago
2026-10-06 16:32:42 -03:00
parent 0952eeb33c
commit 29451f877d
4 changed files with 30 additions and 4 deletions
@@ -1 +1 @@
<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Detalhe do Condominio</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>Detalhe do Condominio</h1></div><section class="panel" id="header"></section><div class="nav-tabs"><button data-tab="contatos" class="active">Contatos</button><button data-tab="espacos">Espacos</button><button data-tab="assembleias">Assembleias</button><button data-tab="documentos">Documentos</button><button data-tab="avisos">Avisos</button></div><section class="panel tab" id="contatos"></section><section class="panel tab hidden" id="espacos"></section><section class="panel tab hidden" id="assembleias"></section><section class="panel tab hidden" id="documentos"></section><section class="panel tab hidden" id="avisos"></section></main><script>Layout.mount('empresa','condominios');</script><script src="/cliente/condominio-detalhe.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>Detalhe do Condominio</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>Detalhe do Condominio</h1></div><section class="panel" id="header"></section><div class="nav-tabs"><button data-tab="contatos" class="active">Contatos</button><button data-tab="espacos">Espacos</button><button data-tab="assembleias">Assembleias</button><button data-tab="documentos">Documentos</button><button data-tab="avisos">Avisos</button><button data-tab="respostas">Respostas</button></div><section class="panel tab" id="contatos"></section><section class="panel tab hidden" id="espacos"></section><section class="panel tab hidden" id="assembleias"></section><section class="panel tab hidden" id="documentos"></section><section class="panel tab hidden" id="avisos"></section><section class="panel tab hidden" id="respostas"></section></main><script>Layout.mount('empresa','condominios');</script><script src="/cliente/condominio-detalhe.js"></script></body></html>
@@ -1,7 +1,8 @@
const id=new URLSearchParams(location.search).get('id');const sections=['contatos','espacos','assembleias','documentos','avisos'];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=[],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=[];function recLabel(v){return {1:'Mensal',3:'Trimestral',12:'Anual'}[Number(v)]||'Sem recorrencia'}
function esc(v){return String(v??'').replace(/[&<>'"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]))}
function lupaBtn(fn,label){return `<button class="search-icon-btn" type="button" onclick="${fn}" aria-label="${label}" 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>`}
function headerCadastro(titulo,tipo,novoFn,extra=''){const q=esc(termoDetalhe(tipo)),open=document.querySelector(`#${tipo}-search`)&&!document.querySelector(`#${tipo}-search`)?.classList.contains('hidden');return `<div class="section-head"><h2>${titulo}</h2><div class="section-actions">${extra}${lupaBtn(`alternarBuscaDetalhe('${tipo}')`,'Buscar '+titulo)}<button class="btn" type="button" onclick="${novoFn}">Novo</button></div></div><div id="${tipo}-search" class="search-strip ${open?'':'hidden'}"><input id="${tipo}-search-input" placeholder="Buscar" value="${q}"></div>`}
function headerConsulta(titulo,tipo,extra=''){const q=esc(termoDetalhe(tipo)),open=document.querySelector(`#${tipo}-search`)&&!document.querySelector(`#${tipo}-search`)?.classList.contains('hidden');return `<div class="section-head"><h2>${titulo}</h2><div class="section-actions">${extra}${lupaBtn(`alternarBuscaDetalhe('${tipo}')`,'Buscar '+titulo)}</div></div><div id="${tipo}-search" class="search-strip ${open?'':'hidden'}"><input id="${tipo}-search-input" placeholder="Buscar" value="${q}"></div>`}
function termoDetalhe(tipo){return document.querySelector(`#${tipo}-search-input`)?.value.trim().toLowerCase()||''}
function contemDetalhe(q,...vals){return !q||vals.some(v=>String(v??'').toLowerCase().includes(q))}
window.alternarBuscaDetalhe=tipo=>{const strip=document.querySelector(`#${tipo}-search`),input=document.querySelector(`#${tipo}-search-input`);if(!strip||!input)return;strip.classList.toggle('hidden');if(!strip.classList.contains('hidden'))input.focus()};
@@ -26,7 +27,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)}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('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');const lista=contatos.filter(c=>contemDetalhe(q,c.contato_nome,c.tipo,c.contato_telefone,c.contato_email));document.querySelector('#contatos').innerHTML=headerCadastro('Contatos','contatos','abrirNovoContato()')+`<div class="table-wrap"><table><thead><tr><th>Nome</th><th>Tipo</th><th>Telefone</th><th>Email</th></tr></thead><tbody>${lista.map(c=>`<tr><td>${esc(c.contato_nome)}</td><td>${esc(c.tipo)}</td><td>${esc(c.contato_telefone||'')}</td><td>${esc(c.contato_email||'')}</td></tr>`).join('')||'<tr><td colspan="4">Nenhum contato encontrado.</td></tr>'}</tbody></table></div>`;document.querySelector('#contatos-search-input')?.addEventListener('input',renderContatos)}
window.abrirNovoContato=()=>{abrirDetailModal('Novo contato','<form id="contato-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required></div><div class="field"><label>Telefone</label><input name="telefone"></div><div class="field"><label>E-mail</label><input name="email"></div><div class="field"><label>Tipo</label><select name="tipo"><option value="sindico">Sindico</option><option value="subsindico">Subsindico</option><option value="conselheiro">Conselheiro</option></select></div><div class="row"><button class="btn">Cadastrar</button><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button></div></form>');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');fecharDetailModal();loadContatos();load()}catch(err){Util.toast(err.message,true)}}};
@@ -48,6 +49,8 @@ function avisoFormHtml(){const destContatos=contatos.map(c=>`<label><input type=
async function loadAvisos(){const d=await Api.get(`/api/condominios/${id}/avisos`);avisos=d.avisos||[];renderAvisos()}
function renderAvisos(){const q=termoDetalhe('avisos');const lista=avisos.filter(a=>contemDetalhe(q,a.titulo,a.status_envio,a.data_evento,a.data_publicacao,recLabel(a.recorrencia_meses)));document.querySelector('#avisos').innerHTML=headerCadastro('Avisos','avisos','abrirNovoAviso()')+`<div class="table-wrap"><table><thead><tr><th>Titulo</th><th>Status</th><th>Envio</th><th>Publicado em</th><th>Recorrencia</th><th>Acoes</th></tr></thead><tbody>${lista.map(a=>`<tr><td>${esc(a.titulo)}</td><td>${Util.badge(a.status_envio)}</td><td>${Util.date(a.data_evento)}</td><td>${Util.date(a.data_publicacao)}</td><td>${recLabel(a.recorrencia_meses)}</td><td><button class="btn secondary" onclick="reenviarAviso(${a.id})">Reenviar</button> <button class="btn danger" onclick="excluirAviso(${a.id})">Excluir</button></td></tr>`).join('')||'<tr><td colspan="6">Nenhum aviso encontrado.</td></tr>'}</tbody></table></div>`;document.querySelector('#avisos-search-input')?.addEventListener('input',renderAvisos)}
window.abrirNovoAviso=()=>{abrirDetailModal('Novo aviso',avisoFormHtml());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');fecharDetailModal();loadAvisos();load()}catch(err){Util.toast(err.message,true)}}};
async function loadRespostas(){const d=await Api.get(`/api/condominios/${id}/respostas`);respostas=d.respostas||[];renderRespostas()}
function renderRespostas(){const q=termoDetalhe('respostas');const lista=respostas.filter(r=>contemDetalhe(q,r.resposta_texto,r.mensagem_original,r.nome_origem,r.telefone_origem,r.contato_nome,r.mensagem_id,r.origem,r.status,r.tipo_origem));document.querySelector('#respostas').innerHTML=headerConsulta('Respostas','respostas')+`<div class="table-wrap"><table><thead><tr><th>Recebido em</th><th>Contato</th><th>Telefone</th><th>Mensagem ID</th><th>Resposta</th><th>Origem</th><th>Status</th></tr></thead><tbody>${lista.map(r=>`<tr><td>${Util.date(r.recebido_em)}</td><td><strong>${esc(r.contato_nome||r.nome_origem||'-')}</strong><br><span class="muted">${esc(r.tipo_origem||'')}</span></td><td>${esc(r.contato_telefone||r.telefone_origem||'')}</td><td>${esc(r.mensagem_id||'')}</td><td>${esc(r.resposta_texto||'')}<br><span class="muted">${esc(r.mensagem_original||'')}</span></td><td>${esc(r.origem||'')}</td><td>${Util.badge(r.status)}</td></tr>`).join('')||'<tr><td colspan="7">Nenhuma resposta registrada.</td></tr>'}</tbody></table></div>`;document.querySelector('#respostas-search-input')?.addEventListener('input',renderRespostas)}
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();