Unifica tela de lembretes

This commit is contained in:
Tiago
2026-10-06 15:08:06 -03:00
parent d624016034
commit 4fa63c54a4
2 changed files with 102 additions and 14 deletions
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Lembrete</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>Lembrete</h1></div><section class="panel"><h2>Novo lembrete</h2><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 hidden" id="cancel-edit" type="button">Cancelar edicao</button></div></form></section><section class="panel"><h2>Lembretes</h2><div class="table-wrap"><table><thead><tr><th>Titulo</th><th>Data/hora</th><th>Status</th><th>Destinatarios</th><th>Acoes</th></tr></thead><tbody id="rows"></tbody></table></div></section></main><script>Layout.mount('empresa','lembretes');</script><script src="/cliente/lembretes.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>Lembrete</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>Lembrete</h1></div><section class="panel"><div class="section-head"><h2>Lembretes</h2><div class="section-actions"><button class="search-icon-btn" type="button" onclick="alternarBuscaLembretes()" aria-label="Buscar lembretes" 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="btn" type="button" onclick="abrirNovoLembrete()">Novo</button></div></div><div id="lembrete-search" class="search-strip hidden"><input id="lembrete-search-input" placeholder="Buscar por titulo, mensagem, status ou destinatario"></div><div class="table-wrap"><table><thead><tr><th>Titulo</th><th>Data/hora</th><th>Status</th><th>Destinatarios</th><th>Acoes</th></tr></thead><tbody id="rows"></tbody></table></div></section><div id="lembrete-modal" class="modal hidden"><div class="modal-backdrop" onclick="fecharLembreteModal()"></div><section class="modal-panel"><div class="modal-head"><h2 id="lembrete-modal-title">Novo lembrete</h2><button class="btn secondary" type="button" onclick="fecharLembreteModal()">Fechar</button></div><div id="lembrete-modal-body"></div></section></div></main><script>Layout.mount('empresa','lembretes');</script><script src="/cliente/lembretes.js"></script></body></html>
+101 -13
View File
@@ -1,20 +1,108 @@
const form=document.querySelector('#lembrete-form'),rows=document.querySelector('#rows'),pessoasEl=document.querySelector('#pessoas'),submitBtn=document.querySelector('#submit-btn'),cancelBtn=document.querySelector('#cancel-edit');
let pessoas=[],lembretes=[];
const rows=document.querySelector('#rows'),modal=document.querySelector('#lembrete-modal'),modalTitle=document.querySelector('#lembrete-modal-title'),modalBody=document.querySelector('#lembrete-modal-body');
let pessoas=[],lembretes=[],form=null,refreshTimer=null;
function esc(v){return String(v??'').replace(/[&<>'"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]))}
function toInputDate(v){if(!v)return'';const d=new Date(v);if(Number.isNaN(d.getTime()))return'';const pad=n=>String(n).padStart(2,'0');return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`}
function recLabel(v){return {1:"Mensal",3:"Trimestral",12:"Anual"}[Number(v)]||"Sem recorrencia"}
function selectedDestIds(){return new Set([...form.querySelectorAll('input[name=dest]:checked')].map(x=>String(x.value)))}
function termo(){return document.querySelector('#lembrete-search-input')?.value.trim().toLowerCase()||''}
function selectedDestIds(){return form?new Set([...form.querySelectorAll('input[name=dest]:checked')].map(x=>String(x.value))):new Set()}
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>';
}
function bindForm(){
form=document.querySelector('#lembrete-form');
renderPessoasForm();
form.onsubmit=async e=>{
e.preventDefault();
const b=Util.formData(form);
b.destinatarios=[...form.querySelectorAll('input[name=dest]:checked')].map(x=>Number(x.value));
try{
if(form.id.value){
await Api.put(`/api/lembretes/${form.id.value}`,b);
Util.toast('Lembrete atualizado');
}else{
const r=await Api.post('/api/lembretes',b);
const en=r.envio||{};
Util.toast(en.enfileirado?'Lembrete enfileirado para envio':(en.falhas?('Lembrete salvo. Enviados: '+(en.enviados||0)+'. Falhas: '+en.falhas):'Lembrete cadastrado'));
}
fecharLembreteModal();
await load();
}catch(err){Util.toast(err.message,true)}
};
}
function renderPessoasForm(){
const pessoasEl=document.querySelector('#pessoas');
if(!pessoasEl)return;
pessoasEl.innerHTML='<button class="btn secondary" type="button" onclick="abrirNotificacao()">Notificação</button> <span id="notif-summary" class="muted">Nenhuma pessoa selecionada</span><div id="pessoas-hidden" class="hidden">'+pessoas.map(p=>'<label><input type="checkbox" name="dest" value="'+p.id+'"> '+esc(p.nome)+' ('+esc(p.cargo||'')+')</label>').join('')+'</div>';
updateNotifSummary();
}
function renderLembretes(){
const q=termo();
const lista=lembretes.filter(l=>{
const dest=(l.destinatarios||[]).map(x=>x.nome).join(' ');
return !q||[l.titulo,l.mensagem,l.status,dest,recLabel(l.recorrencia_meses)].some(v=>String(v??'').toLowerCase().includes(q));
});
rows.innerHTML=lista.map(l=>{
const dest=(l.destinatarios||[]).map(x=>x.nome).join(', ')||'-';
const acoes=l.pode_alterar?`<button class="btn" onclick="enviar(${l.id})">Enviar</button> ${l.pode_editar?`<button class="btn secondary" onclick="editar(${l.id})">Editar</button>`:''} <button class="btn danger" onclick="excluir(${l.id})">Excluir</button>`:'-';
return `<tr><td><strong>${esc(l.titulo)}</strong><br><span class="muted">${esc(l.mensagem)}</span></td><td>${Util.date(l.data_hora)}<br><span class="muted">${recLabel(l.recorrencia_meses)}</span></td><td>${Util.badge(l.status)}</td><td>${esc(dest)}</td><td>${acoes}</td></tr>`;
}).join('')||'<tr><td colspan="5">Nenhum lembrete encontrado.</td></tr>';
}
async function loadPessoas(){const d=await Api.get('/api/lembrete-pessoas');pessoas=d.pessoas||[]}
async function load(){try{const d=await Api.get('/api/lembretes');lembretes=d.lembretes||[];renderLembretes()}catch(e){Util.toast(e.message,true)}}
window.abrirNovoLembrete=()=>{
modalTitle.textContent='Novo lembrete';
modalBody.innerHTML=formHtml();
modal.classList.remove('hidden');
bindForm();
};
window.fecharLembreteModal=()=>{
modal.classList.add('hidden');
modalBody.innerHTML='';
form=null;
};
window.alternarBuscaLembretes=()=>{
const strip=document.querySelector('#lembrete-search'),input=document.querySelector('#lembrete-search-input');
strip.classList.toggle('hidden');
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')}
window.abrirNotificacao=()=>{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.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=()=>{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()};
async function loadPessoas(){const d=await Api.get('/api/lembrete-pessoas');pessoas=d.pessoas||[];pessoasEl.innerHTML='<button class="btn secondary" type="button" onclick="abrirNotificacao()">Notificação</button> <span id="notif-summary" class="muted">Nenhuma pessoa selecionada</span><div id="pessoas-hidden" class="hidden">'+pessoas.map(p=>'<label><input type="checkbox" name="dest" value="'+p.id+'"> '+esc(p.nome)+' ('+esc(p.cargo||'')+')</label>').join('')+'</div>';updateNotifSummary()}
async function load(){try{const d=await Api.get('/api/lembretes');lembretes=d.lembretes||[];rows.innerHTML=lembretes.map(l=>{const dest=(l.destinatarios||[]).map(x=>x.nome).join(', ')||'-';const acoes=l.pode_alterar?`<button class="btn" onclick="enviar(${l.id})">Enviar</button> ${l.pode_editar?`<button class="btn secondary" onclick="editar(${l.id})">Editar</button>`:''} <button class="btn danger" onclick="excluir(${l.id})">Excluir</button>`:'-';return `<tr><td><strong>${esc(l.titulo)}</strong><br><span class="muted">${esc(l.mensagem)}</span></td><td>${Util.date(l.data_hora)}<br><span class="muted">${recLabel(l.recorrencia_meses)}</span></td><td>${Util.badge(l.status)}</td><td>${esc(dest)}</td><td>${acoes}</td></tr>`}).join('')||'<tr><td colspan="5">Nenhum lembrete cadastrado.</td></tr>'}catch(e){Util.toast(e.message,true)}}
function resetForm(){form.reset();form.id.value='';form.recorrencia_meses.value='0';submitBtn.textContent='Cadastrar lembrete';cancelBtn.classList.add('hidden');form.querySelectorAll('input[name=dest]').forEach(x=>x.checked=false);updateNotifSummary()}
window.editar=id=>{const l=lembretes.find(x=>Number(x.id)===Number(id));if(!l)return;if(!l.pode_editar){Util.toast('Este lembrete ja foi enviado e nao pode mais ser editado.',true);return;}form.id.value=l.id;form.titulo.value=l.titulo||'';form.mensagem.value=l.mensagem||'';form.data_hora.value=toInputDate(l.data_hora);form.status.value='ativo';form.recorrencia_meses.value=String(l.recorrencia_meses||0);const ids=new Set((l.destinatarios||[]).map(x=>Number(x.id)));form.querySelectorAll('input[name=dest]').forEach(x=>x.checked=ids.has(Number(x.value)));updateNotifSummary();submitBtn.textContent='Salvar alteracao';cancelBtn.classList.remove('hidden');scrollTo({top:0,behavior:'smooth'})};
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()};
window.editar=id=>{
const l=lembretes.find(x=>Number(x.id)===Number(id));
if(!l)return;
if(!l.pode_editar){Util.toast('Este lembrete ja foi enviado e nao pode mais ser editado.',true);return;}
modalTitle.textContent='Editar lembrete';
modalBody.innerHTML=formHtml();
modal.classList.remove('hidden');
bindForm();
form.id.value=l.id;
form.titulo.value=l.titulo||'';
form.mensagem.value=l.mensagem||'';
form.data_hora.value=toInputDate(l.data_hora);
form.status.value='ativo';
form.recorrencia_meses.value=String(l.recorrencia_meses||0);
const ids=new Set((l.destinatarios||[]).map(x=>Number(x.id)));
form.querySelectorAll('input[name=dest]').forEach(x=>x.checked=ids.has(Number(x.value)));
document.querySelector('#submit-btn').textContent='Salvar alteracao';
updateNotifSummary();
};
window.enviar=async id=>{try{const r=await Api.post(`/api/lembretes/${id}/enviar`,{});const en=r.envio||{};Util.toast(en.falhas?("Enviados: "+(en.enviados||0)+". Falhas: "+en.falhas):("Lembrete enviado para "+(en.enviados||0)));await load()}catch(e){Util.toast(e.message,true)}};
window.excluir=async id=>{try{await Api.request(`/api/lembretes/${id}`,{method:'DELETE'});Util.toast('Lembrete excluido');await load();resetForm()}catch(e){Util.toast(e.message,true)}};
cancelBtn.onclick=resetForm;
form.onsubmit=async e=>{e.preventDefault();const b=Util.formData(form);b.destinatarios=[...form.querySelectorAll('input[name=dest]:checked')].map(x=>Number(x.value));try{if(form.id.value){await Api.put(`/api/lembretes/${form.id.value}`,b);Util.toast('Lembrete atualizado')}else{const r=await Api.post('/api/lembretes',b);const en=r.envio||{};Util.toast(en.enfileirado?'Lembrete enfileirado para envio':(en.falhas?('Lembrete salvo. Enviados: '+(en.enviados||0)+'. Falhas: '+en.falhas):'Lembrete cadastrado'))}resetForm();await load()}catch(err){Util.toast(err.message,true)}};
(async()=>{try{await loadPessoas();await load();setInterval(load,5000)}catch(e){Util.toast(e.message,true)}})();
window.excluir=async id=>{try{await Api.request(`/api/lembretes/${id}`,{method:'DELETE'});Util.toast('Lembrete excluido');await load();fecharLembreteModal()}catch(e){Util.toast(e.message,true)}};
document.querySelector('#lembrete-search-input')?.addEventListener('input',renderLembretes);
(async()=>{try{await loadPessoas();await load();refreshTimer=setInterval(load,5000)}catch(e){Util.toast(e.message,true)}})();