style: padronizar estrutura e layout de popups em todo o sistema com modal-actions

This commit is contained in:
Tiago
2026-10-10 18:27:54 -03:00
parent 258230f1c4
commit db4d4bdb82
5 changed files with 39 additions and 15 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ function dateKey(v){const d=new Date(v);if(Number.isNaN(d.getTime()))return'';co
function monthLabel(d){return d.toLocaleDateString('pt-BR',{month:'long',year:'numeric'})}
function formHtml(r={}){
const dias=new Set(String(r.dias_semana||'').split(','));
return `<form id="responsavel-form" class="modal-form"><input type="hidden" name="id" value="${r.id||''}"><div class="form-grid"><div class="field"><label>Nome</label><input name="nome" required value="${esc(r.nome||'')}"></div><div class="field"><label>WhatsApp</label><input name="telefone" value="${esc(r.telefone||'')}"></div><div class="field"><label>E-mail</label><input name="email" type="email" value="${esc(r.email||'')}"></div><div class="field"><label>Status</label><select name="status"><option value="ativo" ${r.status==='ativo'||!r.status?'selected':''}>Ativo</option><option value="inativo" ${r.status==='inativo'?'selected':''}>Inativo</option></select></div><div class="field field-wide"><label>Dias da semana</label><div class="check-grid">${diasNomes.map((d,i)=>`<label><input type="checkbox" name="dias" value="${i}" ${dias.has(String(i))?'checked':''}> ${d}</label>`).join('')}</div></div></div><div class="modal-actions"><button class="btn secondary" type="button" onclick="fecharResponsavelModal()">Cancelar</button><button class="btn">${r.id?'Salvar':'Cadastrar'}</button></div></form>`;
return `<form id="responsavel-form" class="modal-form"><input type="hidden" name="id" value="${r.id||''}"><div class="form-grid"><div class="field"><label>Nome</label><input name="nome" required value="${esc(r.nome||'')}"></div><div class="field"><label>WhatsApp</label><input name="telefone" value="${esc(r.telefone||'')}"></div><div class="field"><label>E-mail</label><input name="email" type="email" value="${esc(r.email||'')}"></div><div class="field"><label>Status</label><select name="status"><option value="ativo" ${r.status==='ativo'||!r.status?'selected':''}>Ativo</option><option value="inativo" ${r.status==='inativo'?'selected':''}>Inativo</option></select></div><div class="field field-wide"><label>Dias da semana</label><div class="check-grid">${diasNomes.map((d,i)=>`<label><input type="checkbox" name="dias" value="${i}" ${dias.has(String(i))?'checked':''}> ${d}</label>`).join('')}</div></div></div><div class="modal-actions" style="grid-column: 1 / -1;"><button class="btn secondary" type="button" onclick="fecharResponsavelModal()">Cancelar</button><button class="btn" type="submit">${r.id?'Salvar alterações':'Cadastrar responsável'}</button></div></form>`;
}
const Icons = {
@@ -28,7 +28,7 @@ window.mudarMesCalendario=delta=>{calMonth=new Date(calMonth.getFullYear(),calMo
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.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="row"><button class="btn" type="button" onclick="salvarAvisoNotificacao()">Salvar</button><button class="btn secondary" type="button" onclick="fecharAvisoNotificacao()">Cancelar</button></div></section></div>');return document.querySelector('#aviso-notif-modal')}
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')}
function avisoDestInputs(){return [...document.querySelectorAll('#aviso-dests-hidden input[name=dest]')]}
function updateAvisoNotifSummary(){const n=avisoDestInputs().filter(x=>x.checked).length;const el=document.querySelector('#aviso-notif-summary');if(el)el.textContent=n?(n+' selecionado'+(n>1?'s':'')):'Nenhuma pessoa selecionada'}
window.abrirAvisoNotificacao=()=>{const m=ensureAvisoNotifModal(),ids=new Set(avisoDestInputs().filter(x=>x.checked).map(x=>x.value));const pessoas=[...contatos.map(c=>({value:'contato:'+c.contato_id,nome:c.contato_nome,tipo:c.tipo})),...funcionariosVinculados.map(f=>({value:'funcionario:'+(f.funcionario_id||f.id),nome:f.nome,tipo:'funcionario'})),...extras.map(x=>({value:'extra:'+x.id,nome:x.nome,tipo:'extra'}))];document.querySelector('#aviso-notif-body').innerHTML=pessoas.map(p=>'<label><input type="checkbox" value="'+p.value+'" '+(ids.has(p.value)?'checked':'')+'> '+p.nome+' ('+p.tipo+')</label>').join('')||'<p class="muted">Nenhuma pessoa cadastrada.</p>';m.classList.remove('hidden')};
@@ -44,7 +44,7 @@ function renderContatos(){
document.querySelector('#contatos-search-input')?.addEventListener('input',renderContatos);
}
window.desvincularContatoDetalhe=async (cid,tipo)=>{if(!await Util.confirm('Deseja desvincular este contato do condomínio?','Desvincular contato'))return;try{await Api.request(`/api/condominios/${id}/contatos/${cid}?tipo=${encodeURIComponent(tipo)}`,{method:'DELETE'});Util.toast('Contato desvinculado');loadContatos();load()}catch(e){Util.toast(e.message,true)}};
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)}}};
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="modal-actions" style="grid-column: 1 / -1;"><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button><button class="btn" type="submit">Cadastrar contato</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)}}};
async function loadEspacos(){const d=await Api.get(`/api/condominios/${id}/espacos-comuns`);espacos=d.espacos||[];renderEspacos()}
function renderEspacos(){
@@ -53,9 +53,9 @@ function renderEspacos(){
document.querySelector('#espacos').innerHTML=headerCadastro('Espaços comuns','espacos','abrirNovoEspaco()')+`<div class="table-wrap"><table><thead><tr><th>Nome</th><th>Valor</th><th>Índice</th><th>Descrição</th><th style="text-align:right">Ações</th></tr></thead><tbody>${lista.map(x=>`<tr><td>${esc(x.nome)}</td><td>${Util.money(x.valor_utilizacao_centavos)}</td><td>${esc(x.indice_correcao||'')}</td><td>${esc(x.descricao||'')}</td><td style="text-align:right"><div class="table-actions"><button class="table-icon-btn" type="button" onclick="abrirEditarEspaco(${x.id})" title="Editar espaço" aria-label="Editar espaço">${Icons.edit}</button><button class="table-icon-btn danger" type="button" onclick="excluirEspacoDetalhe(${x.id})" title="Excluir espaço" aria-label="Excluir espaço">${Icons.trash}</button></div></td></tr>`).join('')||'<tr><td colspan="5">Nenhum espaço encontrado.</td></tr>'}</tbody></table></div>`;
document.querySelector('#espacos-search-input')?.addEventListener('input',renderEspacos);
}
window.abrirEditarEspaco=eid=>{const x=espacos.find(e=>Number(e.id)===Number(eid));if(!x)return;abrirDetailModal('Editar espaço comum','<form id="espaco-edit-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required value="'+esc(x.nome||'')+'"></div><div class="field"><label>Valor centavos</label><input name="valor_utilizacao_centavos" type="number" value="'+(x.valor_utilizacao_centavos??'')+'"></div><div class="field"><label>Índice</label><input name="indice_correcao" value="'+esc(x.indice_correcao||'')+'"></div><div class="field"><label>Descrição</label><input name="descricao" value="'+esc(x.descricao||'')+'"></div><div class="row"><button class="btn">Salvar</button><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button></div></form>');document.querySelector('#espaco-edit-form').onsubmit=async e=>{e.preventDefault();try{await Api.put(`/api/condominios/${id}/espacos-comuns/${eid}`,Util.formData(e.target));Util.toast('Espaço atualizado');fecharDetailModal();loadEspacos()}catch(err){Util.toast(err.message,true)}}};
window.abrirEditarEspaco=eid=>{const x=espacos.find(e=>Number(e.id)===Number(eid));if(!x)return;abrirDetailModal('Editar espaço comum','<form id="espaco-edit-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required value="'+esc(x.nome||'')+'"></div><div class="field"><label>Valor centavos</label><input name="valor_utilizacao_centavos" type="number" value="'+(x.valor_utilizacao_centavos??'')+'"></div><div class="field"><label>Índice</label><input name="indice_correcao" value="'+esc(x.indice_correcao||'')+'"></div><div class="field"><label>Descrição</label><input name="descricao" value="'+esc(x.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">Salvar alterações</button></div></form>');document.querySelector('#espaco-edit-form').onsubmit=async e=>{e.preventDefault();try{await Api.put(`/api/condominios/${id}/espacos-comuns/${eid}`,Util.formData(e.target));Util.toast('Espaço atualizado');fecharDetailModal();loadEspacos()}catch(err){Util.toast(err.message,true)}}};
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="row"><button class="btn">Cadastrar</button><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</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)}}};
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 loadAssembleias(){const d=await Api.get(`/api/condominios/${id}/assembleias`);assembleias=d.assembleias||[];renderAssembleias()}
function renderAssembleias(){
@@ -66,7 +66,7 @@ function renderAssembleias(){
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="row"><button class="btn">Cadastrar</button><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</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=()=>{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)}}};
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()}
@@ -77,9 +77,9 @@ function renderDocs(){
document.querySelector('#documentos-search-input')?.addEventListener('input',renderDocs);
}
window.excluirDocumentoDetalhe=async did=>{if(!await Util.confirm('Tem certeza que deseja excluir este documento?','Excluir documento'))return;try{await Api.request(`/api/condominios/${id}/documentos/${did}`,{method:'DELETE'});Util.toast('Documento excluído');loadDocs()}catch(err){Util.toast(err.message,true)}};
window.abrirNovoDocumento=()=>{abrirDetailModal('Novo documento','<form id="doc-form" class="row"><div class="field"><label>Tipo</label><input name="tipo"></div><div class="field"><label>Arquivo</label><input name="arquivo" type="file" required></div><div class="row"><button class="btn">Enviar</button><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button></div></form>');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');fecharDetailModal();loadDocs();loadAvisos()}catch(err){Util.toast(err.message,true)}}};
window.abrirNovoDocumento=()=>{abrirDetailModal('Novo documento','<form id="doc-form" class="grid cols"><div class="field"><label>Tipo</label><input name="tipo"></div><div class="field"><label>Arquivo</label><input name="arquivo" type="file" required></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">Enviar documento</button></div></form>');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');fecharDetailModal();loadDocs();loadAvisos()}catch(err){Util.toast(err.message,true)}}};
function avisoFormHtml(){const destContatos=contatos.map(c=>`<label><input type="checkbox" name="dest" value="contato:${c.contato_id}"> ${esc(c.contato_nome)} (${esc(c.tipo)})</label>`).join('');const destFuncionarios=funcionariosVinculados.map(f=>`<label><input type="checkbox" name="dest" value="funcionario:${f.funcionario_id||f.id}"> ${esc(f.nome)} (funcionario)</label>`).join('');const destExtras=extras.map(x=>`<label><input type="checkbox" name="dest" value="extra:${x.id}"> ${esc(x.nome)} (extra)</label>`).join('');const dest=[destContatos,destFuncionarios,destExtras].filter(Boolean).join('');const docChecks=docs.map(d=>`<label><input type="checkbox" name="doc" value="${d.id}"> ${esc(d.nome_arquivo)}</label>`).join('');return `<form id="aviso-form" class="grid cols"><div class="field"><label>Titulo</label><input name="titulo" required></div><div class="field"><label>Tipo</label><input name="tipo" value="aviso"></div><div class="field"><label>Prioridade</label><select name="prioridade"><option>normal</option><option>alta</option><option>urgente</option></select></div><div class="field"><label>Mensagem</label><textarea name="mensagem" required></textarea></div><div class="field"><label>Data/hora de envio</label><input name="data_evento" type="datetime-local"></div><div class="field"><label>Recorrencia</label><select name="recorrencia_meses"><option value="0">Sem recorrencia</option><option value="1">1 vez por mes</option><option value="3">A cada 3 meses</option><option value="12">1 vez por ano</option></select></div><div class="field"><label>Destinatarios</label><button class="btn secondary" type="button" onclick="abrirAvisoNotificacao()">Notificação</button> <span id="aviso-notif-summary" class="muted">Nenhuma pessoa selecionada</span><div id="aviso-dests-hidden" class="hidden">${dest}</div></div><div class="field"><label>Documentos</label>${docChecks||'<p>Nenhum documento.</p>'}</div><label><input type="checkbox" name="enviar_whatsapp" value="true"> Enviar por WhatsApp</label><label><input type="checkbox" name="enviar_email" value="true"> Enviar por e-mail</label><div class="row"><button class="btn">Criar aviso</button><button class="btn secondary" type="button" onclick="fecharDetailModal()">Cancelar</button></div></form>`}
function avisoFormHtml(){const destContatos=contatos.map(c=>`<label><input type="checkbox" name="dest" value="contato:${c.contato_id}"> ${esc(c.contato_nome)} (${esc(c.tipo)})</label>`).join('');const destFuncionarios=funcionariosVinculados.map(f=>`<label><input type="checkbox" name="dest" value="funcionario:${f.funcionario_id||f.id}"> ${esc(f.nome)} (funcionario)</label>`).join('');const destExtras=extras.map(x=>`<label><input type="checkbox" name="dest" value="extra:${x.id}"> ${esc(x.nome)} (extra)</label>`).join('');const dest=[destContatos,destFuncionarios,destExtras].filter(Boolean).join('');const docChecks=docs.map(d=>`<label><input type="checkbox" name="doc" value="${d.id}"> ${esc(d.nome_arquivo)}</label>`).join('');return `<form id="aviso-form" class="grid cols"><div class="field"><label>Titulo</label><input name="titulo" required></div><div class="field"><label>Tipo</label><input name="tipo" value="aviso"></div><div class="field"><label>Prioridade</label><select name="prioridade"><option>normal</option><option>alta</option><option>urgente</option></select></div><div class="field" style="grid-column: 1 / -1;"><label>Mensagem</label><textarea name="mensagem" required></textarea></div><div class="field"><label>Data/hora de envio</label><input name="data_evento" type="datetime-local"></div><div class="field"><label>Recorrencia</label><select name="recorrencia_meses"><option value="0">Sem recorrencia</option><option value="1">1 vez por mes</option><option value="3">A cada 3 meses</option><option value="12">1 vez por ano</option></select></div><div class="field" style="grid-column: 1 / -1;"><label>Destinatarios</label><div style="display:flex;gap:10px;align-items:center;"><button class="btn secondary" type="button" onclick="abrirAvisoNotificacao()">Notificação</button> <span id="aviso-notif-summary" class="muted">Nenhuma pessoa selecionada</span></div><div id="aviso-dests-hidden" class="hidden">${dest}</div></div><div class="field" style="grid-column: 1 / -1;"><label>Documentos</label>${docChecks||'<p>Nenhum documento.</p>'}</div><div style="grid-column: 1 / -1; display:flex; gap:16px;"><label><input type="checkbox" name="enviar_whatsapp" value="true"> Enviar por WhatsApp</label><label><input type="checkbox" name="enviar_email" value="true"> Enviar por e-mail</label></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">Criar aviso</button></div></form>`}
async function loadAvisos(){const d=await Api.get(`/api/condominios/${id}/avisos`);avisos=d.avisos||[];renderAvisos()}
function renderAvisos(){
const q=termoDetalhe('avisos');
+3 -4
View File
@@ -5,7 +5,7 @@ function esc(v){return String(v??'').replace(/[&<>'"]/g,c=>({'&':'&amp;','<':'&l
function termo(id){return document.querySelector(id)?.value.trim().toLowerCase()||''}
function contem(item,q,campos){return !q||campos.some(c=>String(item[c]??'').toLowerCase().includes(q))}
function abrirModal(titulo,html){modalTitle.textContent=titulo;modalBody.innerHTML=html;modal.classList.remove('hidden')}
function formActions(label){return '<div class="row"><button class="btn">'+label+'</button><button class="btn secondary" type="button" onclick="fecharModalCondominios()">Cancelar</button></div>'}
function formActions(label){return `<div class="modal-actions" style="grid-column: 1 / -1;"><button class="btn secondary" type="button" onclick="fecharModalCondominios()">Cancelar</button><button class="btn" type="submit">${label}</button></div>`}
const Icons = {
eye: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>',
@@ -96,10 +96,9 @@ window.abrirNovoFuncionario=()=>{
function funcionarioPorId(funcId){return funcionarios.find(x=>Number(x.id)===Number(funcId))}
window.abrirEditarFuncionario=funcId=>{const f=funcionarioPorId(funcId);if(!f)return;abrirModal('Editar funcionario','<form id="func-edit-form" class="grid cols"><div class="field"><label>Nome</label><input name="nome" required value="'+esc(f.nome||'')+'"></div><div class="field"><label>E-mail de login</label><input name="email" type="email" required value="'+esc(f.email||'')+'"></div><div class="field"><label>WhatsApp</label><input name="telefone" value="'+esc(f.telefone||'')+'"></div><div class="field"><label>Status</label><select name="status"><option value="ativo" '+(f.status==='ativo'?'selected':'')+'>Ativo</option><option value="inativo" '+(f.status==='inativo'?'selected':'')+'>Inativo</option></select></div><div class="field"><label>Nova senha</label><input name="senha" type="password"></div>'+formActions('Salvar')+'</form>');document.querySelector('#func-edit-form').onsubmit=async e=>{e.preventDefault();try{await Api.put('/api/funcionarios/'+funcId,Util.formData(e.target));Util.toast('Funcionario atualizado');fecharModalCondominios();loadFuncionarios()}catch(err){Util.toast(err.message,true)}}};
window.abrirExcluirFuncionario=funcId=>{const f=funcionarioPorId(funcId);if(!f)return;abrirModal('Excluir funcionario','<p>Deseja realmente excluir o acesso de <strong>'+esc(f.nome||'')+'</strong>?</p><p class="muted">O funcionario sera inativado e perdera acesso ao sistema.</p><div class="row"><button class="btn danger" type="button" onclick="confirmarExcluirFuncionario('+funcId+')">Excluir</button><button class="btn secondary" type="button" onclick="fecharModalCondominios()">Cancelar</button></div>')};
window.confirmarExcluirFuncionario=async funcId=>{try{await Api.request('/api/funcionarios/'+funcId,{method:'DELETE'});Util.toast('Funcionario inativado');fecharModalCondominios();loadFuncionarios()}catch(err){Util.toast(err.message,true)}};
window.abrirExcluirFuncionario=async funcId=>{const f=funcionarioPorId(funcId);if(!f)return;if(!await Util.confirm('Deseja realmente excluir o acesso de "'+esc(f.nome||'')+'"? O funcionário será inativado e perderá o acesso ao sistema.','Excluir funcionário'))return;try{await Api.request('/api/funcionarios/'+funcId,{method:'DELETE'});Util.toast('Funcionário inativado');loadFuncionarios()}catch(err){Util.toast(err.message,true)}};
function renderModalCondominios(){const f=funcionarios.find(x=>Number(x.id)===Number(funcAtual));if(!f)return;const vinculados=funcLinks.get(Number(funcAtual))||[];const vinculadosIds=new Set(vinculados.map(c=>Number(c.id)));const disponiveis=condominios.filter(c=>!vinculadosIds.has(Number(c.id)));modalTitle.textContent=`Condominios de ${f.nome}`;const listaVinculados=vinculados.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn danger" type="button" onclick="desvincularFuncionarioCondominio(${funcAtual},${c.id})">Desvincular</button></div>`).join('')||'<p class="muted">Nenhum condominio vinculado.</p>';const listaDisponiveis=disponiveis.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn secondary" type="button" onclick="vincularFuncionarioCondominio(${funcAtual},${c.id})">Vincular</button></div>`).join('')||'<p class="muted">Todos os condominios ja estao vinculados.</p>';modalBody.innerHTML=`<div class="modal-grid"><section><h3>Vinculados</h3>${listaVinculados}</section><section><h3>Nao vinculados</h3>${listaDisponiveis}</section></div>`}
function renderModalCondominios(){const f=funcionarios.find(x=>Number(x.id)===Number(funcAtual));if(!f)return;const vinculados=funcLinks.get(Number(funcAtual))||[];const vinculadosIds=new Set(vinculados.map(c=>Number(c.id)));const disponiveis=condominios.filter(c=>!vinculadosIds.has(Number(c.id)));modalTitle.textContent=`Condomínios de ${f.nome}`;const listaVinculados=vinculados.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn danger" type="button" onclick="desvincularFuncionarioCondominio(${funcAtual},${c.id})">Desvincular</button></div>`).join('')||'<p class="muted">Nenhum condomínio vinculado.</p>';const listaDisponiveis=disponiveis.map(c=>`<div class="cond-item"><span>${esc(c.nome)}</span><button class="btn secondary" type="button" onclick="vincularFuncionarioCondominio(${funcAtual},${c.id})">Vincular</button></div>`).join('')||'<p class="muted">Todos os condomínios já estão vinculados.</p>';modalBody.innerHTML=`<div class="modal-grid"><section><h3>Vinculados</h3>${listaVinculados}</section><section><h3>Não vinculados</h3>${listaDisponiveis}</section></div><div class="modal-actions" style="margin-top: 18px;"><button class="btn secondary" type="button" onclick="fecharModalCondominios()">Fechar</button></div>`}
window.abrirModalCondominios=async funcId=>{funcAtual=Number(funcId);try{await refreshLinks();renderModalCondominios();modal.classList.remove('hidden')}catch(err){Util.toast(err.message,true)}};
window.fecharModalCondominios=()=>{modal.classList.add('hidden');funcAtual=null;modalBody.innerHTML=''};
window.vincularFuncionarioCondominio=async (funcId,condId)=>{try{await Api.post(`/api/condominios/${condId}/funcionarios-vinculados`,{funcionario_id:funcId});Util.toast('Funcionario vinculado');await refreshLinks();renderModalCondominios()}catch(err){Util.toast(err.message,true)}};
+2 -2
View File
@@ -9,7 +9,7 @@ function selectedDestIds(){return form?new Set([...form.querySelectorAll('input[
function updateNotifSummary(){const n=selectedDestIds().size;const el=document.querySelector('#notif-summary');if(el)el.textContent=n?(n+' selecionado'+(n>1?'s':'')):'Nenhuma pessoa selecionada'}
function formHtml(){
return '<form id="lembrete-form" class="grid cols"><input type="hidden" name="id"><div class="field"><label>Titulo</label><input name="titulo" required></div><div class="field"><label>Data/hora</label><input name="data_hora" type="datetime-local"></div><div class="field"><label>Status</label><select name="status"><option value="ativo">Ativo</option><option value="concluido">Concluido</option><option value="cancelado">Cancelado</option></select></div><div class="field"><label>Recorrencia</label><select name="recorrencia_meses"><option value="0">Sem recorrencia</option><option value="1">1 vez por mes</option><option value="3">A cada 3 meses</option><option value="12">1 vez por ano</option></select></div><div class="field"><label>Mensagem</label><textarea name="mensagem" required></textarea></div><div class="field"><label>Pessoas que receberao</label><div id="pessoas"></div></div><div class="row"><button class="btn" id="submit-btn">Cadastrar lembrete</button><button class="btn secondary" type="button" onclick="fecharLembreteModal()">Cancelar</button></div></form>';
return '<form id="lembrete-form" class="grid cols"><input type="hidden" name="id"><div class="field"><label>Título</label><input name="titulo" required placeholder="Título do lembrete"></div><div class="field"><label>Data/hora</label><input name="data_hora" type="datetime-local"></div><div class="field"><label>Status</label><select name="status"><option value="ativo">Ativo</option><option value="concluido">Concluído</option><option value="cancelado">Cancelado</option></select></div><div class="field"><label>Recorrência</label><select name="recorrencia_meses"><option value="0">Sem recorrência</option><option value="1">1 vez por mês</option><option value="3">A cada 3 meses</option><option value="12">1 vez por ano</option></select></div><div class="field" style="grid-column: 1 / -1;"><label>Mensagem</label><textarea name="mensagem" required placeholder="Texto da mensagem do lembrete"></textarea></div><div class="field" style="grid-column: 1 / -1;"><label>Pessoas que receberão</label><div id="pessoas"></div></div><div class="modal-actions" style="grid-column: 1 / -1;"><button class="btn secondary" type="button" onclick="fecharLembreteModal()">Cancelar</button><button class="btn" id="submit-btn" type="submit">Cadastrar lembrete</button></div></form>';
}
function bindForm(){
@@ -90,7 +90,7 @@ window.alternarBuscaLembretes=()=>{
if(!strip.classList.contains('hidden'))input.focus();
};
function ensureNotifModal(){let m=document.querySelector('#notif-modal');if(m)return m;document.body.insertAdjacentHTML('beforeend','<div id="notif-modal" class="modal hidden"><div class="modal-backdrop" onclick="fecharNotificacao()"></div><section class="modal-panel"><div class="modal-head"><h2>Notificação</h2><button class="btn secondary" type="button" onclick="fecharNotificacao()">Fechar</button></div><div id="notif-modal-body"></div><div class="row"><button class="btn" type="button" onclick="salvarNotificacao()">Salvar</button><button class="btn secondary" type="button" onclick="fecharNotificacao()">Cancelar</button></div></section></div>');return document.querySelector('#notif-modal')}
function ensureNotifModal(){let m=document.querySelector('#notif-modal');if(m)return m;document.body.insertAdjacentHTML('beforeend','<div id="notif-modal" class="modal hidden"><div class="modal-backdrop" onclick="fecharNotificacao()"></div><section class="modal-panel"><div class="modal-head"><h2>Notificação</h2><button class="btn secondary" type="button" onclick="fecharNotificacao()">Fechar</button></div><div id="notif-modal-body"></div><div class="modal-actions"><button class="btn secondary" type="button" onclick="fecharNotificacao()">Cancelar</button><button class="btn" type="button" onclick="salvarNotificacao()">Salvar</button></div></section></div>');return document.querySelector('#notif-modal')}
window.abrirNotificacao=()=>{if(!form)return;const m=ensureNotifModal(),ids=selectedDestIds();document.querySelector('#notif-modal-body').innerHTML=pessoas.map(p=>'<label><input type="checkbox" value="'+p.id+'" '+(ids.has(String(p.id))?'checked':'')+'> '+esc(p.nome)+' ('+esc(p.cargo||'')+')</label>').join('')||'<p class="muted">Nenhum funcionario ativo.</p>';m.classList.remove('hidden')};
window.fecharNotificacao=()=>document.querySelector('#notif-modal')?.classList.add('hidden');
window.salvarNotificacao=()=>{if(!form)return;const ids=new Set([...document.querySelectorAll('#notif-modal-body input:checked')].map(x=>String(x.value)));form.querySelectorAll('input[name=dest]').forEach(x=>x.checked=ids.has(String(x.value)));updateNotifSummary();fecharNotificacao()};
+26 -1
View File
@@ -853,19 +853,44 @@ th {
}
.modal-actions {
grid-column: 1 / -1;
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
flex-wrap: wrap;
padding-top: 16px;
padding-top: 18px;
border-top: 1px solid var(--line);
margin-top: 14px;
}
.modal-actions .btn {
min-width: 120px;
}
.modal-panel form.grid.cols,
.modal-panel form.grid {
align-items: start;
}
.modal-panel form > .row:last-child {
grid-column: 1 / -1;
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
flex-wrap: wrap;
padding-top: 18px;
border-top: 1px solid var(--line);
margin-top: 14px;
}
.modal-panel form > .row:last-child .btn {
min-width: 120px;
}
.confirm-panel {
width: min(460px, 100%);
}