`);w.document.close()} function eventPage(){ const e=state.event;if(!e)return `
Selecione um evento.
`; const d=e.event_date||e.date,t=e.event_time||e.time||''; if(me.role==='NOIVA'){ return `

${esc(e.name)}

${fmtDate(d)} às ${String(t).slice(0,5)} · ${esc(e.venue||'A definir')} · ${esc(e.type||'Evento')}

${renderEventTab()}`; } return `

${esc(e.name)}

${fmtDate(d)} às ${String(t).slice(0,5)} · ${esc(e.venue||'A definir')} · ${esc(e.type||'Evento')}

Cód. do evento: ${String(e.id||"").slice(0,8).toUpperCase()}
${me.role==='ASSESSORIA'?`
`:''}
${renderEventTab()}` } function renderEventTab(){if(state.eventTab==='home')return eventHome();if(state.eventTab==='guests')return guestsPage();if(state.eventTab==='tasks')return tasksPage();if(state.eventTab==='briefing')return briefingPage();if(state.eventTab==='script')return scriptPage();if(state.eventTab==='attachments')return attachmentsPage();return ''} function eventHome(){ const e=state.event,conf=Number(e.confirmed_snapshot||0),goal=Number(e.guest_goal||state.guests.length||0),pending=Math.max(0,goal-conf),tasksTotal=Number(e.tasks_total_snapshot||state.tasks.length||0),tasksDone=Number(e.tasks_done_snapshot||state.tasks.filter(t=>t.status==='concluida').length||0),present=state.guests.filter(g=>g.checked_in).length; return `
${esc(e.type)} · ${esc(e.status)}

${esc(e.name)}

${fmtDate(e.event_date)} às ${String(e.event_time||'').slice(0,5)} · ${esc(e.venue||'A definir')}

Convidados previstos
${goal}
${conf} confirmados
RSVP pendentes
${pending}
${pct(conf,goal)}% respondidos/confirmados
Tarefas
${tasksDone}/${tasksTotal}
concluídas
Presentes
${present}
check-ins realizados

Andamento dos convidados

Confirmados${conf}
Pendentes${pending}

Assessoria e organização

Fornecedores vinculados${state.eventSuppliers.length}
Serviços ativos${state.eventServices.length}

Documentação

Anexos enviados${state.attachments.length}
Briefing${state.briefings.length?'Criado':'Pendente'}
Roteiro${state.scripts.length?'Criado':'Pendente'}
`} function guestsPage(){return `
${me.role==='ASSESSORIA'?``:''}
${state.guestTab==='list'?guestList():state.guestTab==='rsvp'?rsvpPage():state.guestTab==='tables'?tablesPage():checkinPage(true)}`} function inviteGroups(){ const map=new Map(); state.guests.forEach(g=>{const title=(g.invite_name||g.name||'Convite').trim(),phone=(g.phone||'').trim(),key=(title+'␟'+phone).toLowerCase();if(!map.has(key))map.set(key,{key,title,phone,group:g.group_name||'Convidados',notes:g.notes||'',guests:[]});map.get(key).guests.push(g)}); return [...map.values()].sort((a,b)=>a.title.localeCompare(b.title,'pt-BR')); } function guestCreationText(g){const dt=g?.created_at?new Date(String(g.created_at).replace(' ','T')):null;const when=dt&&!isNaN(dt)?dt.toLocaleString('pt-BR',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}):'data não disponível';return `Criado em ${when} por ${g?.created_by_name||'Cadastro anterior'}`} function guestList(){ const conf=state.guests.filter(g=>g.rsvp==='confirmado').length,pend=state.guests.filter(g=>g.rsvp==='pendente').length,rec=state.guests.filter(g=>g.rsvp==='recusado').length; state.selectedGuests=state.selectedGuests||[];state.guestViewMode=state.guestViewMode||'invite';const groups=inviteGroups(); const isGuest=state.guestViewMode==='guest'; const toolbar=`
${state.guestViewMenuOpen?`
`:''}
${state.guestAddOpen?`
`:''}
${state.guestMoreOpen?`
`:''}
`; let rows=''; if(isGuest){ rows=state.guests.slice().sort((a,b)=>String(a.name).localeCompare(String(b.name),'pt-BR')).map(g=>{const iv=groups.find(iv=>iv.guests.some(x=>x.id===g.id));const ik=iv?.key||'';const pending=g.rsvp==='pendente',ok=g.rsvp==='confirmado',no=g.rsvp==='recusado';const sit=`
!✓×
`;return `
Convite: ${esc(g.invite_name||g.name)}
${esc(g.group_name||'Convidados')}${esc(g.table_name||'Sem mesa')}${sit}`}).join(''); }else{ rows=groups.map(iv=>{const c=iv.guests.filter(g=>g.rsvp==='confirmado').length,pd=iv.guests.filter(g=>g.rsvp==='pendente').length,rc=iv.guests.filter(g=>g.rsvp==='recusado').length,names=iv.guests.map(g=>g.name).join(' '),status=[c?'confirmado':'',pd?'pendente':'',rc?'recusado':''].join(' '),oldest=iv.guests.slice().sort((a,b)=>String(a.created_at||'').localeCompare(String(b.created_at||'')))[0],tip=guestCreationText(oldest);return `
${esc(iv.phone||'')}
${esc(tip)}
${esc(iv.group)}
!${pd}✓${c}×${rc}
`}).join(''); } const allSelected=state.guests.length>0&&state.guests.every(g=>state.selectedGuests.includes(g.id));const master=` `;const head=isGuest?`${master}ConvidadoGrupoMesaSituaçãoAções`:`${master}ConviteGrupoRespostasAções`; const adults=state.guests.filter(g=>/adult/i.test(g.age_group||'')).length,elderly=state.guests.filter(g=>/idos/i.test(g.age_group||'')).length,teen=state.guests.filter(g=>/adolesc/i.test(g.age_group||'')).length,children=state.guests.filter(g=>/crian(?!.*colo)/i.test(g.age_group||'')).length,babies=state.guests.filter(g=>/colo|beb[eê]/i.test(g.age_group||'')).length,male=state.guests.filter(g=>/masc|homem/i.test(g.gender||'')).length,female=state.guests.filter(g=>/fem|mulher/i.test(g.gender||'')).length; const summary=``; return `${toolbar}
${head}${rows}
${summary}
`; } function rsvpPage(){const e=state.event;return `

Envio personalizado de confirmação de presença

Cada convidado possui um link individual para confirmar presença.

Mensagem padrão

Olá, [nome]!\n\nVocê está convidado(a) para ${esc(e.name)}.\nData: ${fmtDate(e.event_date)} às ${String(e.event_time||'').slice(0,5)}.\n\nConfirme sua presença pelo link individual do seu convite.
${state.guests.map(g=>``).join('')}
ConvidadoStatusLink
${esc(g.name)}${g.rsvp}
`} function tableAgeKey(g){return String(g?.age_group||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().trim()} function tablesPage(){const un=state.guests.filter(g=>!g.table_id);const children=state.guests.filter(g=>tableAgeKey(g)==='crianca').length;const lapChildren=state.guests.filter(g=>tableAgeKey(g).includes('crianca de colo')||tableAgeKey(g).includes('bebe')||tableAgeKey(g).includes('colo')).length;const occupiedTables=state.tables.filter(t=>state.guests.some(g=>g.table_id===t.id)).length;return `

Mapeamento de mesas

Arraste os nomes entre as mesas. A alteração é salva automaticamente ao soltar.
👧
${children}Crianças
🍼
${lapChildren}Crianças de colo
▱
${occupiedTables}Mesas ocupadas
👤
${un.length}Convidados sem lugar

Sem mesa

${un.length} convidados
${un.map(chip).join('')}
${state.tables.map(t=>{const gs=state.guests.filter(g=>g.table_id===t.id);return `

${esc(t.name)}

${gs.length} de ${t.capacity} lugares
${gs.map(chip).join('')}
`}).join('')}
`} function chip(g){return `
${esc(g.name)}
`} function tasksPage(){const services=[...new Set(state.tasks.map(t=>t.service).filter(Boolean))];const responsible=[...new Set(state.tasks.map(t=>t.responsible).filter(Boolean))];const tasks=state.tasks.filter(t=>(!state.taskSearch||t.title.toLowerCase().includes(state.taskSearch.toLowerCase()))&&(!state.taskStatus||t.status===state.taskStatus)&&(!state.taskService||t.service===state.taskService)&&(!state.taskResponsible||t.responsible===state.taskResponsible)&&(!state.taskDue||String(t.due_date||'').slice(0,10)===state.taskDue)&&(!state.taskChecklist||t.has_checklist)&&(!state.taskQuestions||t.has_questions));const groups=[...new Set(tasks.map(t=>t.period_label||'SEM PERÍODO'))];const board=(status)=>tasks.filter(t=>t.status===status).map(t=>`
${esc(t.title)}
${fmtShort(t.due_date)} · ${esc(t.service||'Sem serviço')}
`).join('')||'
Nenhuma tarefa
';return `
Visualizar em:
${tasks.length} tarefas encontradas
${state.taskView==='status'?`
${['pendente','andamento','atrasada','concluida'].map(st=>`

${statusLabel(st)}

${board(st)}
`).join('')}
`:state.taskView==='day'?`
${[...new Set(tasks.map(t=>String(t.due_date||'Sem prazo').slice(0,10)))].map(d=>`

${d==='Sem prazo'?d:fmtShort(d)}

${tasks.filter(t=>String(t.due_date||'Sem prazo').slice(0,10)===d).map(t=>`
${esc(t.title)}
${esc(t.service||'—')}
`).join('')}
`).join('')}
`:groups.map(gr=>`
${esc(gr)}${tasks.filter(t=>(t.period_label||'SEM PERÍODO')===gr).length} tarefas⌄
${tasks.filter(t=>(t.period_label||'SEM PERÍODO')===gr).map(t=>`
${esc(t.title)}
${fmtShort(t.due_date)}
${esc(t.service||'—')}${t.responsible?`
${esc(t.responsible)}`:''}
`).join('')}
`).join('')||'
Nenhuma tarefa encontrada.
'}
`} function briefingPage(){const b=state.briefings[0];return `
${b?`

${esc(b.title)}

PerguntaResposta
${(b.sections||[]).map((sec,si)=>`

${esc(sec.title)}

${esc(sec.description||'')}

${(sec.questions||[]).map((qq,qi)=>`
${qq.type==='long'?``:``}
`).join('')}
`).join('')}`:`
Nenhum briefing criado para este evento.
`}
`} function scriptPage(){const sc=state.scripts[0];return `
${sc?`
${esc(state.event.name)}${fmtDate(state.event.event_date)}
${esc(sc.title)}
${(sc.sections||[]).map((sec,i)=>`

◉   ${esc(sec.title)}

□   Adicionar tópico
`).join('')}`:`
Nenhum roteiro criado.
`}
`} function attachmentsPage(){const active=state.attachmentsTab||'suppliers';return `

Anexos do evento

Informações e documentos organizados exclusivamente para ${esc(state.event.name)}.
${active==='suppliers'?eventSuppliersPage():active==='services'?eventServicesPage():active==='tasks'?eventAttachmentTasksPage():eventNotesPage()}`} function eventSuppliersPage(){const available=state.suppliers.filter(s=>!state.eventSuppliers.some(es=>es.id===s.id));return `

Fornecedores do evento

Somente fornecedores vinculados a ${esc(state.event.name)}.
${state.eventSuppliers.map(s=>`
F
${esc(s.name)}${(s.services||[]).map(esc).join(' · ')||'Sem serviços informados'}
`).join('')||'
Nenhum fornecedor vinculado a este evento.
'}
`} function eventServicesPage(){return `

Serviços do evento

Consolidados a partir dos fornecedores vinculados e das tarefas de ${esc(state.event.name)}.
${state.eventServices.map(s=>`${esc(s.name)}`).join('')||'
Nenhum serviço vinculado ainda.
'}
`} function eventAttachmentTasksPage(){return `

Tarefas vinculadas ao evento

Atalhos das tarefas de ${esc(state.event.name)}.
${state.tasks.slice(0,30).map(t=>`
${esc(t.title)}${fmtShort(t.due_date)} · ${esc(t.service||'Sem serviço')} · ${statusLabel(t.status)}
`).join('')||'
Nenhuma tarefa cadastrada neste evento.
'}
`} function eventNotesPage(){return `

Observações gerais

Informações internas e alinhamentos específicos deste evento.
`} function checkinPage(inner=false){const e=state.event||state.events.find(x=>x.id===me.eventId)||state.events[0];if(!e)return `
Nenhum evento disponível para check-in.
`;const portariaSelector=me.role==='PORTARIA'?`
PORTARIA

Selecione o evento para realizar o check-in

Este acesso pode operar a portaria de todos os eventos da assessoria.

`:'';const q=(state.presenceSearch||'').toLowerCase(),filter=state.presenceFilter||'';const list=state.guests.filter(g=>(!q||g.name.toLowerCase().includes(q)||(g.group_name||'').toLowerCase().includes(q))&&(!filter||(filter==='present'?g.checked_in:filter==='pending'?!g.checked_in&&g.rsvp==='confirmado':g.rsvp==='recusado')));const liveConf=state.guests.filter(g=>g.rsvp==='confirmado').length,confirmed=Math.max(Number(e.confirmed_snapshot||0),liveConf),goal=Math.max(Number(e.guest_goal||0),state.guests.length),rsvpPending=Math.max(0,goal-confirmed-state.guests.filter(g=>g.rsvp==='recusado').length),present=state.guests.filter(g=>g.checked_in).length,pendingArrival=Math.max(0,confirmed-present),noShow=state.guests.filter(g=>g.rsvp==='recusado').length;return `${portariaSelector}${inner?'':`
Portaria

Check-in · ${esc(e.name)}

${fmtDate(e.event_date)}
`}
Convite
Grupo
Ações
${list.map(g=>`
${esc(g.name)}(${inviteSize(g)} convidado${inviteSize(g)>1?'s':''})
${esc(g.group_name||'Convidados')}
${g.checked_in?'Presente':g.rsvp==='recusado'?'Não irá':``}
`).join('')||'
Nenhum convite encontrado.
'}
`} function funnelPage(){return `
Dashboard › Funil de vendas

Funil de vendas

Interesses captados pelo RSVP quando um convidado sinaliza que também pretende realizar um evento.
Novos interesses
${state.leads.length}
originados de convites/RSVP
Próxima ação
Contato
qualificar interesse e data
Origem
Eventos
indicações da base de convidados
${state.leads.map(l=>``).join('')||``}
ContatoEvento de origemData do eventoTelefoneStatus
${esc(l.name)}${esc(l.event_name||'—')}${fmtShort(l.event_date)}${esc(l.phone||'—')}Novo interesse
Nenhum interesse captado até agora.
`} function suppliersPage(){return `
Dashboard › Serviços e fornecedores

Serviços e fornecedores

Catálogo operacional da assessoria.
${state.suppliers.map(s=>``).join('')}
FornecedorServiçosCadastroAções
${esc(s.name)}${(s.services||[]).map(x=>`${esc(x)}`).join(' ')}${esc(s.status)}
`} function modelsPage(){return `
Dashboard › Modelos

Modelos de Gestão de Eventos *

Biblioteca reutilizável. Cronograma e Inspirações não fazem parte desta versão, conforme os itens marcados com X nas referências.

Modelos de Evento *

Checklist 15 AnosEstrutura base para debutantes
Padrão
Checklist de CasamentoEstrutura base para casamentos
Padrão

Modelos de Briefing *

${state.briefingTemplates.map(m=>`
${esc(m.name)}${esc(m.event_type||'Evento')}
${m.is_default?'Padrão':'Personalizado'}
`).join('')}

Modelos de Roteiro *

${state.scriptTemplates.map(m=>`
${esc(m.name)}${esc(m.event_type||'Evento')}
${m.is_default?'Padrão':'Personalizado'}
`).join('')}

Modelos de Mensagens *

RSVP — confirmação de presençaMensagem personalizada com link individual
Padrão
Lembrete de confirmaçãoReenvio para convidados pendentes
Padrão
`} function profilesPage(){return `
Dashboard › Assistentes

Assistentes, perfis e acessos

Controle de acesso separado para assessoria, noiva e portaria.

Assessoria

Portal da Noiva

Portaria

${state.users.map(u=>``).join('')}
NomeWhatsApp / E-mailPerfilEventoStatusAcesso
${esc(u.name)}${esc(u.role==='NOIVA'?(u.phone||'—'):(u.email||'—'))}${roleLabel[u.role]}${esc(state.events.find(e=>e.id===u.event_id)?.name||'Todos')}${u.active?'Ativo':'Inativo'}
`} function modal(){if(!state.modal)return '';const m=state.modal; if(m.type==='confirm-delete')return ``; if(m.type==='install')return ``; if(m.type==='table-create')return ``;if(m.type==='tables-create')return ``;if(m.type==='print-events')return ``; if(m.type==='invite'){const iv=inviteGroups().find(x=>x.key===m.key)||inviteGroups()[Number(m.index)];if(!iv)return '';const tab=m.tab||'data';const rows=iv.guests.map(g=>`
${esc(g.name)}
${esc(g.age_group||'Faixa etária não informada')} - ${esc(g.gender||'Gênero não informado')}
`).join('');const created=iv.guests.map(g=>g.created_at).filter(Boolean).sort()[0];const checked=iv.guests.filter(g=>g.checked_in&&g.checkin_at);const phone=String(iv.phone||'').replace(/\D/g,'');const msg=encodeURIComponent(`Olá! Segue o convite para ${iv.title}. Código: ${iv.guests[0]?.invite_code||''}`);const dataPane=`
Dados do convite
🇧🇷+55⌄
Convidados
${rows||'
Nenhum convidado cadastrado. Informe os dados de pelo menos um convidado para este convite.
'}
`;const histItems=[created?`
♙
${esc(iv.title)}
${esc(String(created).replace('T',' ').slice(0,16))}

Convite cadastrado no sistema

`:'',...iv.guests.map(g=>`
♙
${esc(g.name)}
${esc(g.rsvp==='confirmado'?'Presença confirmada':g.rsvp==='recusado'?'Não comparecerá':'Aguardando confirmação')}
`),...checked.map(g=>`
♙
${esc(g.name)}
${esc(String(g.checkin_at).replace('T',' ').slice(0,16))}

Check-in realizado

`)].filter(Boolean).join('');const historyPane=`
${histItems||'
Ainda não há histórico registrado para este convite.
'}
`;const qrPane=`

QR Code do convite para apresentar na recepção do evento.

Salvar
`;return ``;}if(m.type==='confirm-invite-delete'){const qty=(m.ids||[]).length;return ``;}if(m.type==='confirm-guest-delete'){const qty=(m.ids||[]).length;const preview=(m.names||[]).slice(0,5).map(x=>`
  • ${esc(x)}
  • `).join('');return ``;}if(m.type==='qr'){const g=state.guests.find(x=>x.id===m.id);return ``}if(m.type==='event'){const e=m.event||null;return ``}if(m.type==='guest'){const g=m.guest||null;return ``}if(m.type==='import-guests')return ``;if(m.type==='task')return ``;if(m.type==='supplier')return ``;if(m.type==='password')return ``;if(m.type==='access')return ``;if(m.type==='credentials')return ``;return ''} async function loadEventData(){if(!state.event)return;const id=state.event.id,admin=me.role==='ASSESSORIA';state.guests=await api(`/events/${id}/guests`);state.tables=await api(`/events/${id}/tables`);if(admin){state.tasks=await api(`/events/${id}/tasks`);state.briefings=await api(`/events/${id}/briefing`);state.scripts=await api(`/events/${id}/scripts`);state.attachments=await api(`/events/${id}/attachments`);state.eventSuppliers=await api(`/events/${id}/suppliers`);state.eventServices=await api(`/events/${id}/services`);state.eventNote=await api(`/events/${id}/notes/general`);state.suppliers=await api('/suppliers')}else{state.tasks=[];state.briefings=[];state.scripts=[];state.attachments=[];state.eventSuppliers=[];state.eventServices=[];state.eventNote={content:''};state.suppliers=[]}} async function openEvent(id){state.event=state.events.find(e=>e.id===id)||await api('/events/'+id);state.view='event';state.eventTab=me.role==='NOIVA'?'guests':'home';state.guestTab='list';state.guestViewMode='invite';state.guestViewMenuOpen=false;await loadEventData();render()} async function loadAdmin(v){if(v==='events'){[state.statistics,state.suppliers]=await Promise.all([api('/statistics/events'),api('/suppliers')])}if(v==='funnel')state.leads=await api('/rsvp-opportunities');if(v==='suppliers'){state.suppliers=await api('/suppliers');state.services=await api('/services')}if(v==='models'){[state.briefingTemplates,state.scriptTemplates]=await Promise.all([api('/briefing-templates'),api('/script-templates')])}if(v==='profiles')state.users=await api('/users')} function setMobileMenu(open){ if(window.innerWidth>900)open=false; state.sideOpen=!!open; const side=document.querySelector('.side'); const back=document.querySelector('.mobile-nav-backdrop'); side?.classList.toggle('open',state.sideOpen); back?.classList.toggle('open',state.sideOpen); document.body.classList.toggle('mobile-nav-open',state.sideOpen); } function closeMobileMenu(){setMobileMenu(false)} function bind(){document.body.onclick=async ev=>{const b=ev.target.closest('[data-a]');if(!b)return;const a=b.dataset.a;try{ if(a==='mobile-menu'){ev.preventDefault();if(window.innerWidth<=900)state.sideCollapsed=false;setMobileMenu(!state.sideOpen);return}else if(a==='close-mobile-menu'){ev.preventDefault();closeMobileMenu();return}else if(a==='toggle-side'){if(window.innerWidth<=900){closeMobileMenu();return}state.sideCollapsed=!state.sideCollapsed;localStorage.setItem('sideCollapsed',state.sideCollapsed?'1':'0');render()}else if(a==='go-back'){if(state.view==='event'){state.view='events';state.event=null;await loadAdmin('events')}else{state.view='dashboard'}render()} else if(a==='install-app'){ if(deferredInstallPrompt){deferredInstallPrompt.prompt();await deferredInstallPrompt.userChoice;deferredInstallPrompt=null} else{state.modal={type:'install'};render()} } else if(a==='login'){const identifier=$('#identifier')?.value?.trim()||'',password=$('#pass')?.value||'';state.loginError='';if(!identifier||!password){state.loginError=!identifier&&!password?'Informe o e-mail/WhatsApp e a senha.':(!identifier?'Informe o e-mail ou WhatsApp.':'Informe a senha.');render();setTimeout(()=>{if(!identifier)$('#identifier')?.closest('.field')?.classList.add('has-error');if(!password)$('#pass')?.closest('.field')?.classList.add('has-error')},0);return;}let d;try{d=await api('/auth/login',{method:'POST',body:JSON.stringify({identifier,password})})}catch(loginErr){const msg=loginErr?.status===401?'E-mail/WhatsApp ou senha incorretos. Verifique os dados e tente novamente.':(loginErr?.status===403?'Este acesso está desativado. Entre em contato com a administradora.':(loginErr?.message||'Não foi possível conectar ao servidor. Tente novamente.'));state.loginError=msg;render();setTimeout(()=>{$('#identifier')?.closest('.field')?.classList.add('has-error');$('#pass')?.closest('.field')?.classList.add('has-error');$('#pass')?.focus()},0);return;}token=d.token;me=d.user;localStorage.setItem('token',token);localStorage.setItem('me',JSON.stringify(me));state.events=await api('/events');if(me.role==='PORTARIA'){state.event=state.events[0]||null;state.portariaEventId=state.event?.id||'';if(state.event)await loadEventData();state.view='checkin'}else if(me.eventId){state.event=state.events.find(e=>e.id===me.eventId);await loadEventData();state.view='event';state.eventTab='guests'}render()}else if(a==='logout'){localStorage.clear();token='';me=null;location.reload()}else if(a==='nav'){if(window.innerWidth<=900)closeMobileMenu();state.view=b.dataset.v;if(b.dataset.tab){state.eventTab=b.dataset.tab;if(b.dataset.guestTab)state.guestTab=b.dataset.guestTab;if(!state.event&&me.eventId)state.event=state.events.find(e=>e.id===me.eventId);await loadEventData()}await loadAdmin(state.view);render()}else if(a==='open-event'){if(window.innerWidth<=900)closeMobileMenu();await openEvent(b.dataset.id)}else if(a==='tab'){state.eventTab=b.dataset.tab;await loadEventData();render()}else if(a==='open-invite'){state.modal={type:'invite',key:b.dataset.key||inviteGroups()[Number(b.dataset.index)]?.key,tab:'data'};render()}else if(a==='invite-tab'){if(state.modal?.type==='invite'){state.modal.tab=b.dataset.tab||'data';render()}}else if(a==='save-invite'){const iv=inviteGroups().find(x=>x.key===b.dataset.key)||inviteGroups()[Number(b.dataset.index)];if(!iv)return;const title=$('#inviteTitle')?.value?.trim();if(!title)return window.showAppError?.('Informe o nome do convite.','Dados incompletos');const phone=$('#invitePhone')?.value?.trim()||'',group=$('#inviteGroup')?.value?.trim()||'Convidados',notes=$('#inviteNotes')?.value||'';for(const g of iv.guests){const rsvp=document.querySelector(`.invite-rsvp[data-id="${g.id}"]`)?.value||g.rsvp;await api(`/events/${state.event.id}/guests/${g.id}`,{method:'PATCH',body:JSON.stringify({inviteName:title,phone,groupName:group,notes,rsvp})})}await loadEventData();state.modal=null;render();show('Convite atualizado com sucesso.')}else if(a==='invite-whatsapp'){const phone=b.dataset.phone||'',message=decodeURIComponent(b.dataset.message||'');if(!phone)return window.showAppError?.('Este convite não possui telefone cadastrado.','WhatsApp indisponível');window.open(`https://wa.me/${phone}?text=${encodeURIComponent(message)}`,'_blank')}else if(a==='download-invite-qr'){const canvas=document.querySelector('#inviteQrBox canvas');const img=document.querySelector('#inviteQrBox img');const url=canvas?.toDataURL?.('image/png')||img?.src;if(!url)return window.showAppError?.('Não foi possível gerar o arquivo do QR Code.','Erro no QR Code');const a=document.createElement('a');a.href=url;a.download='qr-code-convite.png';a.click()}else if(a==='toggle-guest-more'){state.guestMoreOpen=!state.guestMoreOpen;render()}else if(a==='remove-duplicate-guests'){if(!confirm('Remover cadastros duplicados exatos deste evento? Será mantido um registro de cada convidado/convite.'))return;const r=await api(`/events/${state.event.id}/guests/deduplicate`,{method:'POST'});await loadEventData();state.guestMoreOpen=false;render();show(r.deleted?`${r.deleted} duplicidade(s) removida(s).`:'Nenhuma duplicidade encontrada.')}else if(a==='copy-unique-rsvp'){const r=await api(`/events/${state.event.id}/rsvp-link`,{method:'POST'});const url=location.origin+r.path;try{await navigator.clipboard.writeText(url);show('Link único de RSVP copiado.')}catch{prompt('Copie o link único de RSVP:',url)}state.guestMoreOpen=false;render()}else if(a==='guest-tab'){state.guestTab=b.dataset.tab;render()}else if(a==='event-filter'){state.eventFilter=b.dataset.filter;if(state.eventFilter==='Estatísticas')state.statistics=await api('/statistics/events');render()}else if(a==='print-events'){state.modal={type:'print-events'};render()}else if(a==='confirm-print-events'){const f=document.querySelector('input[name=printFilter]:checked')?.value||'Todos';state.modal=null;render();printEventsReport(f)}else if(a==='event-edit-row'){const e=state.events.find(x=>x.id===b.dataset.id);state.modal={type:'event',event:e};render()}else if(a==='event-reopen'){await api('/events/'+b.dataset.id,{method:'PATCH',body:JSON.stringify({status:'Em andamento'})});state.events=await api('/events');state.statistics=await api('/statistics/events');state.eventFilter='Em andamento';render();show('Evento reaberto.')}else if(a==='event-remove'){const e=state.events.find(x=>x.id===b.dataset.id);if(!confirm('Remover definitivamente o evento '+(e?.name||'')+'? Esta ação também remove os dados vinculados.'))return;await api('/events/'+b.dataset.id,{method:'DELETE'});state.events=await api('/events');state.statistics=await api('/statistics/events');render();show('Evento removido.')}else if(a==='new-event'){state.modal={type:'event'};render()}else if(a==='edit-event'){state.modal={type:'event',event:state.event};render()}else if(a==='save-event'){if(!$('#ename').value||!$('#edate').value)return show('Informe nome e data.');const f=$('#ephoto').files[0];if(!f)return show('A foto do evento é obrigatória.');const r=await api('/events',{method:'POST',body:JSON.stringify({name:$('#ename').value,type:$('#etype').value,eventDate:$('#edate').value,eventTime:$('#etime').value,venue:$('#evenue').value,brideName:$('#ebrideName').value||undefined,bridePhone:$('#ebridePhone').value||undefined,brideEmail:$('#ebrideEmail').value||undefined})});const fd=new FormData();fd.append('photo',f);await api(`/events/${r.event.id}/photo`,{method:'POST',body:fd});state.events=await api('/events');state.modal=r.brideAccess?{type:'credentials',phone:r.brideAccess.phone,email:r.brideAccess.email,password:r.brideAccess.password,whatsappUrl:r.brideAccess.whatsappUrl,message:r.brideAccess.message}:null;render();show('Evento criado com foto.')}else if(a==='save-edit-event'){const targetId=state.modal?.event?.id||state.event?.id;if(!targetId)return show('Evento não encontrado.');const updated=await api(`/events/${targetId}`,{method:'PATCH',body:JSON.stringify({name:$('#ename').value,type:$('#etype').value,eventDate:$('#edate').value,eventTime:$('#etime').value,venue:$('#evenue').value})});if(state.event?.id===targetId)state.event=updated;state.events=await api('/events');state.modal=null;render();show('Evento atualizado.')}else if(a==='offline-event'){if(!state.event)return;try{show('Baixando dados do evento para uso offline...');await loadEventData();await EventOffline.downloadEvent(state.event,state.attachments||[]);show('Evento disponível offline neste dispositivo.');render()}catch(e){throw e}}else if(a==='photo')$('#photo').click();else if(a==='new-guest'){state.guestAddOpen=false;state.modal={type:'guest'};render()}else if(a==='open-invite'){state.modal={type:'invite',key:b.dataset.key||inviteGroups()[Number(b.dataset.index)]?.key,tab:'data'};render()}else if(a==='save-invite'){const iv=inviteGroups().find(x=>x.key===b.dataset.key)||inviteGroups()[Number(b.dataset.index)];if(!iv)return;const title=$('#inviteTitle')?.value?.trim();if(!title)throw Error('Informe o nome do convite.');const phone=$('#invitePhone')?.value?.trim()||'',group=$('#inviteGroup')?.value?.trim()||'Convidados',notes=$('#inviteNotes')?.value?.trim()||'';for(const g of iv.guests){const sel=document.querySelector(`.invite-rsvp[data-id="${g.id}"]`);await api(`/events/${state.event.id}/guests/${g.id}`,{method:'PATCH',body:JSON.stringify({inviteName:title,phone,groupName:group,notes,rsvp:sel?.value||g.rsvp})})}state.modal=null;await loadEventData();render();show('Convite atualizado com sucesso.')}else if(a==='set-invite-rsvp'){const g=state.guests.find(x=>x.id===b.dataset.id);if(g){g.rsvp=b.dataset.status;const sel=document.querySelector(`.invite-rsvp[data-id="${g.id}"]`);if(sel)sel.value=g.rsvp;document.querySelectorAll(`.vip-rsvp-icon[data-id="${g.id}"]`).forEach(x=>x.classList.toggle('active',x.dataset.status===g.rsvp));}}else if(a==='edit-guest'){state.modal={type:'guest',guest:state.guests.find(g=>g.id===b.dataset.id)};render()}else if(a==='save-guest'){const id=b.dataset.id,payload={name:$('#gname').value,inviteName:$('#ginviteName')?.value||$('#gname').value,phone:$('#gphone').value,groupName:$('#ggroup').value,ageGroup:$('#gageGroup')?.value||null,rsvp:$('#grsvp').value};if(id)await api(`/events/${state.event.id}/guests/${id}`,{method:'PATCH',body:JSON.stringify(payload)});else await api(`/events/${state.event.id}/guests`,{method:'POST',body:JSON.stringify(payload)});state.modal=null;await loadEventData();render();show('Convidado salvo.')}else if(a==='qr'){state.modal={type:'qr',id:b.dataset.id};render();setTimeout(()=>{const g=state.guests.find(x=>x.id===b.dataset.id);new QRCode($('#qr'),{text:g.invite_code,width:180,height:180,colorDark:'#111',colorLight:'#fff'})},0)}else if(a==='copy-rsvp'){const link=location.origin+'/rsvp.html?code='+encodeURIComponent(b.dataset.code);try{await navigator.clipboard.writeText(link);show('Link de RSVP copiado.')}catch{show(link)}}else if(a==='new-table'){state.modal={type:'table-create'};render()}else if(a==='new-tables'){state.modal={type:'tables-create'};render()}else if(a==='save-table'){const name=$('#tableName')?.value?.trim(),capacity=Math.max(1,Number($('#tableCapacity')?.value||8));if(!name)return show('Informe o nome da mesa.');await api(`/events/${state.event.id}/tables`,{method:'POST',body:JSON.stringify({name,capacity})});state.tables=await api(`/events/${state.event.id}/tables`);state.modal=null;render();show('Mesa criada com sucesso.')}else if(a==='save-tables'){const qty=Math.min(100,Math.max(1,Number($('#tablesQty')?.value||1))),capacity=Math.max(1,Number($('#tablesCapacity')?.value||8)),start=Math.max(1,Number($('#tablesStart')?.value||1));for(let i=0;ix.key===b.dataset.key)||inviteGroups()[Number(b.dataset.index)];if(!iv)return;state.modal={type:'confirm-invite-delete',title:iv.title,ids:iv.guests.map(g=>g.id)};render()}else if(a==='confirm-invite-delete'){const ids=state.modal?.ids||[];if(!ids.length)return;await api(`/events/${state.event.id}/guests/bulk-delete`,{method:'POST',body:JSON.stringify({ids})});state.modal=null;state.selectedGuests=[];await loadEventData();render();show('Convite excluído com sucesso.')}else if(a==='delete-guest'){const id=b.dataset.id,name=b.dataset.name||'este convidado';state.modal={type:'confirm-guest-delete',ids:[id],names:[name]};render()}else if(a==='delete-selected-guests'){const ids=[...(state.selectedGuests||[])];if(!ids.length)return window.showAppError?.('Selecione pelo menos um convidado.','Nenhum convidado selecionado');const names=state.guests.filter(g=>ids.includes(g.id)).map(g=>g.name);state.modal={type:'confirm-guest-delete',ids,names};render()}else if(a==='confirm-guest-delete'){const ids=state.modal?.ids||[];if(!ids.length)return;await api(`/events/${state.event.id}/guests/bulk-delete`,{method:'POST',body:JSON.stringify({ids})});state.selectedGuests=[];state.modal=null;await loadEventData();render();show(ids.length===1?'Convidado excluído com sucesso.':`${ids.length} convidados excluídos com sucesso.`)}else if(a==='task-modal-tab'){ }else if(a==='add-task-question'){const c=$('#taskQuestionsList');if(c)c.insertAdjacentHTML('beforeend','
    ')}else if(a==='add-task-checkitem'){const c=$('#taskChecklistList');if(c)c.insertAdjacentHTML('beforeend','
    ')}else if(a==='new-task'){state.modal={type:'task'};render()}else if(a==='save-task'){const title=$('#ttitle')?.value?.trim();if(!title)return show('Informe o título da tarefa.');const questions=[...document.querySelectorAll('.task-q-input')].map(x=>x.value.trim()).filter(Boolean);const checklist=[...document.querySelectorAll('.task-c-input')].map(x=>x.value.trim()).filter(Boolean);await api(`/events/${state.event.id}/tasks`,{method:'POST',body:JSON.stringify({title,dueDate:$('#tdue')?.value||null,service:$('#tservice')?.value||null,responsible:$('#tresp')?.value||null,periodLabel:$('#tperiod')?.value||null,status:$('#tstatus')?.value||'pendente',notes:$('#tdesc')?.innerHTML||'',hasChecklist:checklist.length>0,hasQuestions:questions.length>0,checklist,questions})});state.modal=null;state.tasks=await api(`/events/${state.event.id}/tasks`);render();show('Tarefa adicionada.')}else if(a==='toggle-task'){const t=state.tasks.find(x=>x.id===b.dataset.id);await api(`/events/${state.event.id}/tasks/${t.id}`,{method:'PATCH',body:JSON.stringify({status:t.status==='concluida'?'pendente':'concluida'})});state.tasks=await api(`/events/${state.event.id}/tasks`);render()}else if(a==='print-briefing'){window.open(`/briefing-print.html?eventId=${encodeURIComponent(state.event.id)}&briefingId=${encodeURIComponent(b.dataset.id)}`,'_blank')}else if(a==='share-briefing'){const url=location.href;try{if(navigator.share)await navigator.share({title:state.briefings[0]?.title||'Briefing',url});else{await navigator.clipboard.writeText(url);show('Link do briefing copiado.')}}catch{} }else if(a==='briefing-preview'){window.open(`/briefing-print.html?eventId=${encodeURIComponent(state.event.id)}&briefingId=${encodeURIComponent(state.briefings[0]?.id||'')}`,'_blank')}else if(a==='duplicate-briefing'){const br=state.briefings[0];if(!br)return;await api(`/events/${state.event.id}/briefing/from-template`,{method:'POST',body:JSON.stringify({templateId:br.template_id})});state.briefings=await api(`/events/${state.event.id}/briefing`);render();show('Briefing duplicado.')}else if(a==='remove-briefing'){state.modal={type:'confirm-delete',kind:'briefing',id:b.dataset.id};render()}else if(a==='print-script'){window.open(`/script-print.html?eventId=${encodeURIComponent(state.event.id)}&scriptId=${encodeURIComponent(b.dataset.id)}`,'_blank')}else if(a==='share-script'){const url=location.href;try{if(navigator.share)await navigator.share({title:state.scripts[0]?.title||'Roteiro',url});else{await navigator.clipboard.writeText(url);show('Link do roteiro copiado.')}}catch{} }else if(a==='duplicate-script'){const sc=state.scripts[0];if(!sc)return;await api(`/events/${state.event.id}/scripts/from-template`,{method:'POST',body:JSON.stringify({templateId:sc.template_id})});state.scripts=await api(`/events/${state.event.id}/scripts`);render();show('Roteiro duplicado.')}else if(a==='remove-script'){state.modal={type:'confirm-delete',kind:'script',id:b.dataset.id};render()}else if(a==='add-script-section'){const sc=state.scripts[0];if(!sc)return;sc.sections.push({title:'Insira o título da seção (o que vai acontecer nesse momento)',time:'',music:'',supplier:'',notes:'Clique aqui para inserir as descrições/observações desse momento.',topics:[]});await api(`/events/${state.event.id}/scripts/${sc.id}`,{method:'PATCH',body:JSON.stringify({sections:sc.sections})});state.scripts=await api(`/events/${state.event.id}/scripts`);render()}else if(a==='duplicate-script-section'){const sc=state.scripts[0],i=Number(b.dataset.index);if(!sc?.sections?.[i])return;sc.sections.splice(i+1,0,JSON.parse(JSON.stringify(sc.sections[i])));await api(`/events/${state.event.id}/scripts/${sc.id}`,{method:'PATCH',body:JSON.stringify({sections:sc.sections})});state.scripts=await api(`/events/${state.event.id}/scripts`);render()}else if(a==='remove-script-section'){const sc=state.scripts[0],i=Number(b.dataset.index);if(!sc?.sections?.[i])return;if(!confirm('Remover esta seção do roteiro?'))return;sc.sections.splice(i,1);await api(`/events/${state.event.id}/scripts/${sc.id}`,{method:'PATCH',body:JSON.stringify({sections:sc.sections})});state.scripts=await api(`/events/${state.event.id}/scripts`);render()}else if(a==='print-script-section'){window.open(`/script-print.html?eventId=${encodeURIComponent(state.event.id)}&scriptId=${encodeURIComponent(state.scripts[0]?.id||'')}§ion=${encodeURIComponent(b.dataset.index)}`,'_blank')}else if(a==='confirm-delete'){if(b.dataset.kind==='briefing'){await api(`/events/${state.event.id}/briefing/${b.dataset.id}`,{method:'DELETE'});state.briefings=await api(`/events/${state.event.id}/briefing`)}else{await api(`/events/${state.event.id}/scripts/${b.dataset.id}`,{method:'DELETE'});state.scripts=await api(`/events/${state.event.id}/scripts`)}state.modal=null;render();show('Removido com sucesso.')}else if(a==='save-briefing'){const br=state.briefings[0];document.querySelectorAll('[data-bq]').forEach(el=>{const [si,qi]=el.dataset.bq.split(':').map(Number);br.sections[si].questions[qi].answer=el.value});await api(`/events/${state.event.id}/briefing/${br.id}`,{method:'PATCH',body:JSON.stringify({sections:br.sections})});show('Briefing salvo.')}else if(a==='new-briefing'){if(!state.briefingTemplates.length)state.briefingTemplates=await api('/briefing-templates');const t=state.briefingTemplates[0];if(!t)return show('Nenhum modelo disponível.');await api(`/events/${state.event.id}/briefing/from-template`,{method:'POST',body:JSON.stringify({templateId:t.id})});state.briefings=await api(`/events/${state.event.id}/briefing`);render()}else if(a==='save-script'){const sc=state.scripts[0];document.querySelectorAll('[data-sfield]').forEach(el=>{const [i,k]=el.dataset.sfield.split(':');sc.sections[Number(i)][k]=el.value});await api(`/events/${state.event.id}/scripts/${sc.id}`,{method:'PATCH',body:JSON.stringify({sections:sc.sections})});show('Roteiro salvo.')}else if(a==='new-script'){if(!state.scriptTemplates.length)state.scriptTemplates=await api('/script-templates');const t=state.scriptTemplates[0];if(!t)return show('Nenhum modelo disponível.');await api(`/events/${state.event.id}/scripts/from-template`,{method:'POST',body:JSON.stringify({templateId:t.id})});state.scripts=await api(`/events/${state.event.id}/scripts`);render()}else if(a==='upload-attachment')$('#attachFile').click();else if(a==='delete-attachment'){await api(`/events/${state.event.id}/attachments/${b.dataset.id}`,{method:'DELETE'});state.attachments=await api(`/events/${state.event.id}/attachments`);render()}else if(a==='checkin'){const code=$('#checkCode')?.value;if(!code)return show('Digite o código do convite.');await api(`/events/${state.event.id}/checkin`,{method:'POST',body:JSON.stringify({inviteCode:code,method:'Código manual'})});await loadEventData();render();show('Entrada confirmada.')}else if(a==='new-supplier'){state.modal={type:'supplier'};render()}else if(a==='edit-supplier'){state.modal={type:'supplier',data:state.suppliers.find(s=>s.id===b.dataset.id)};render()}else if(a==='save-supplier'){const id=b.dataset.id,p={name:$('#spname').value,services:$('#spservices').value.split(',').map(x=>x.trim()).filter(Boolean),status:'completo'};if(id)await api('/suppliers/'+id,{method:'PATCH',body:JSON.stringify(p)});else await api('/suppliers',{method:'POST',body:JSON.stringify(p)});state.suppliers=await api('/suppliers');state.modal=null;render()}else if(a==='new-access'){state.modal={type:'access'};render()}else if(a==='change-password'){const u=state.users.find(x=>x.id===b.dataset.id);if(!u)return;state.modal={type:'password',user:u};render()}else if(a==='save-password'){const p1=$('#newPassword')?.value||'',p2=$('#newPasswordConfirm')?.value||'';if(p1.length<8)return show('A senha deve ter pelo menos 8 caracteres.');if(p1!==p2)return show('As senhas não coincidem.');await api(`/users/${b.dataset.id}/password`,{method:'PATCH',body:JSON.stringify({password:p1})});state.modal=null;render();show('Senha alterada com sucesso.')}else if(a==='save-access'){const role=$('#urole').value;const r=await api('/users',{method:'POST',body:JSON.stringify({name:$('#uname').value,email:$('#uemail').value||undefined,phone:$('#uphone').value||undefined,role,eventId:$('#uevent').value})});state.users=await api('/users');state.modal={type:'credentials',phone:r.user.phone,email:r.user.email,password:r.password,whatsappUrl:r.whatsappUrl,message:r.message};render()}else if(a==='close'){state.modal=null;render()}}catch(err){if(window.showAppError)window.showAppError(err,'Não foi possível concluir');else show(err.message)}}; const photo=$('#photo');if(photo)photo.onchange=async()=>{const f=photo.files[0];if(!f)return;const fd=new FormData();fd.append('photo',f);state.event=await api(`/events/${state.event.id}/photo`,{method:'POST',body:fd});state.events=await api('/events');render();show('Foto atualizada.')};const af=$('#attachFile');if(af)af.onchange=async()=>{const f=af.files[0];if(!f)return;const fd=new FormData();fd.append('file',f);await api(`/events/${state.event.id}/attachments`,{method:'POST',body:fd});state.attachments=await api(`/events/${state.event.id}/attachments`);render();show('Arquivo adicionado.')};const es=$('#eventSearch');if(es)es.oninput=()=>document.querySelectorAll('[data-event-name]').forEach(r=>r.style.display=r.dataset.eventName.includes(es.value.toLowerCase())?'':'none');const gs=$('#guestSearch'),gf=$('#guestFilter');const applyGuest=()=>{if(!gs||!gf)return;document.querySelectorAll('#guestRows tr').forEach(r=>r.style.display=(r.dataset.gname.includes(gs.value.toLowerCase())&&(!gf.value||r.dataset.rsvp===gf.value))?'':'none')};if(gs)gs.oninput=applyGuest;if(gf)gf.onchange=applyGuest;const ts=$('#taskSearch');if(ts)ts.onchange=()=>{state.taskSearch=ts.value;render()};const tsvc=$('#taskService');if(tsvc)tsvc.onchange=()=>{state.taskService=tsvc.value;render()};const tr=$('#taskResponsible');if(tr)tr.onchange=()=>{state.taskResponsible=tr.value;render()};const td=$('#taskDue');if(td)td.onchange=()=>{state.taskDue=td.value;render()};const tc=$('#taskChecklist');if(tc)tc.onchange=()=>{state.taskChecklist=tc.checked;render()};const tq=$('#taskQuestions');if(tq)tq.onchange=()=>{state.taskQuestions=tq.checked;render()};document.querySelectorAll('input[name=taskStatus]').forEach(r=>r.onchange=()=>{state.taskStatus=r.value;render()});const pe=$('#portariaEventSelect');if(pe)pe.onchange=async()=>{state.portariaEventId=pe.value;state.event=state.events.find(e=>e.id===pe.value)||null;if(state.event)await loadEventData();state.presenceSearch='';state.presenceFilter='';render()};const ps=$('#presenceSearch');if(ps)ps.onchange=()=>{state.presenceSearch=ps.value;render()};const pf=$('#presenceFilter');if(pf)pf.onchange=()=>{state.presenceFilter=pf.value;render()}} function setupDnD(){let gid=null;document.querySelectorAll('.guest-chip').forEach(c=>{c.ondragstart=()=>{gid=c.dataset.guest;c.classList.add('dragging')};c.ondragend=()=>c.classList.remove('dragging')});document.querySelectorAll('[data-table]').forEach(t=>{t.ondragover=e=>{e.preventDefault();t.classList.add('dragover')};t.ondragleave=()=>t.classList.remove('dragover');t.ondrop=async e=>{e.preventDefault();t.classList.remove('dragover');if(!gid)return;try{await api(`/events/${state.event.id}/guests/${gid}/table`,{method:'PATCH',body:JSON.stringify({tableId:t.dataset.table||null})});await loadEventData();render();show('Convidado movido e salvo.')}catch(err){show(err.message)}}})} async function boot(){state.sideCollapsed=localStorage.getItem('sideCollapsed')==='1';if(!me){render();return}try{state.events=await api('/events');if(me.role==='PORTARIA'){state.event=state.events[0]||null;state.portariaEventId=state.event?.id||'';if(state.event)await loadEventData();state.view='checkin'}else if(me.eventId){state.event=state.events.find(e=>e.id===me.eventId);await loadEventData();state.view='event';state.eventTab='guests'}render()}catch{localStorage.clear();token='';me=null;render()}} boot();