`);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; // V121: os indicadores da Home são sempre calculados a partir da lista real carregada. // Assim qualquer confirmação feita pelo RSVP individual, RSVP geral ou pelo portal // do contratante aparece imediatamente, sem depender de snapshots antigos do evento. const goal=state.guests.length; const conf=state.guests.filter(g=>g.rsvp==='confirmado').length; const pending=state.guests.filter(g=>(g.rsvp||'pendente')==='pendente').length; const declined=state.guests.filter(g=>g.rsvp==='recusado').length; const responded=conf+declined; const tasksTotal=state.tasks.length; const tasksDone=state.tasks.filter(t=>t.status==='concluida').length; const 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(responded,goal)}% responderam
Tarefas
${tasksDone}/${tasksTotal}
concluídas
Presentes
${present}
check-ins realizados

Andamento dos convidados

Confirmados${conf}
Pendentes${pending}
Não comparecerão${declined}

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=g.invite_id?('id:'+g.invite_id):((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 guestAlert=state.guestLoadError?`
Os convidados não foram carregados. ${esc(state.guestLoadError)} Nenhum registro foi excluído. Não importe nem exclua convidados até restabelecer a conexão.
`:''; 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}
${state.guestActionsOpen159===g.id?`
`:''}
`}).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 vipStats173=(items,title,tone)=>{ const fare=(name)=>items.filter(g=>String(g.fare_type||'').toLowerCase().includes(name)).length; const unknownFare=items.filter(g=>!g.fare_type||!['inteira','meia','gratuita','gratuito'].some(x=>String(g.fare_type).toLowerCase().includes(x))).length; const age=(term)=>items.filter(g=>String(g.age_group||'').toLowerCase().includes(term)).length; const gender=(terms)=>items.filter(g=>terms.some(x=>String(g.gender||'').toLowerCase()===x)).length; const m=gender(['m','masculino','homem']),f=gender(['f','feminino','mulher']); const ageUnknown=items.length-items.filter(g=>/idos|adult|adolesc|crian|colo|beb[eê]/i.test(g.age_group||'')).length; return ``; }; const summary=(me.role==='ASSESSORIA'||me.role==='ADMIN'||me.role==='ADMINISTRADOR')?`
${vipStats173(state.guests,'convidados','total')}${vipStats173(state.guests.filter(g=>g.rsvp!=='confirmado'&&g.rsvp!=='recusado'),'pendentes','pending')}${vipStats173(state.guests.filter(g=>g.rsvp==='confirmado'),'confirmados','confirmed')}${vipStats173(state.guests.filter(g=>g.rsvp==='recusado'),'não comparecerão','declined')}
`:''; return `${guestAlert}${toolbar}
${head}${rows}
${summary}
`; } // Pesquisa de telefone compatível com números exibidos nos cartões (DDD, 55, espaços e pontuação). function phoneSearchMatch(phone, term){ const onlyDigits=v=>String(v??'').replace(/\D/g,''); const number=onlyDigits(phone), query=onlyDigits(term); if(!query || !number)return false; const variants=[number]; if(number.startsWith('55')&&number.length>=12)variants.push(number.slice(2)); else if(number.length===10||number.length===11)variants.push('55'+number); // Prefixo nacional 0 antes do DDD não deve impedir a localização. if(number.startsWith('0')&&number.length>=11)variants.push(number.slice(1)); return variants.some(v=>v.includes(query)); } function normalizeBrazilWhatsApp(value){const raw=String(value||'').trim();let n=raw.replace(/\D/g,'');if(!n)throw Error('Informe um telefone com DDD.');if(n.startsWith('00'))n=n.slice(2);if(n.startsWith('55')&&(n.length===12||n.length===13))return n;if(n.length===10||n.length===11)return '55'+n;throw Error('Telefone inválido para envio brasileiro. Informe DDD + número (10 ou 11 dígitos), ou +55. Confira o cadastro antes de enviar.');} function rsvpInviteGroups(){ const map=new Map(); state.guests.forEach(g=>{ const title=(g.invite_name||g.name||'Convite').trim(); const key=g.invite_id?('id:'+g.invite_id):((title+'␟'+String(g.phone||'')).toLowerCase()); if(!map.has(key))map.set(key,{key,title,phone:'',guests:[]}); const iv=map.get(key);iv.guests.push(g);if(!iv.phone&&g.phone)iv.phone=g.phone; }); return [...map.values()].sort((a,b)=>a.title.localeCompare(b.title,'pt-BR')); } function rsvpStatusButtons(g){ const st=g.rsvp||'pendente'; return `
`; } let rsvpTemplatesCache=[]; let rsvpDispatchCache={}; async function loadRsvpDispatches(){if(!state.event)return;const rows=await api(`/events/${state.event.id}/rsvp-dispatches`);rsvpDispatchCache=Object.fromEntries(rows.map(r=>[r.invite_key,r]));} function rsvpDispatchLabel(key){return ({iniciado:"Envio iniciado",enviado:"Enviado"})[rsvpDispatchCache[key]?.status]||"Envio pendente";} async function loadRsvpTemplates(){rsvpTemplatesCache=await api('/message-templates');return rsvpTemplatesCache;} function rsvpTemplateBody(){return rsvpTemplatesCache.find(x=>Number(x.is_default)===1)?.body||'';} function rsvpMessageFor(iv){ const e=state.event||{}; const link=location.origin+'/rsvp/'+(iv.guests[0]?.invite_code||''); const template=rsvpTemplateBody(); if(template)return template.replace(/\{(nome_convite|nome_evento|nome_do_contratante|prazo_rsvp|nome_da_empresa|data_evento|hora_evento|link_rsvp)\}|#evento\b/gi,(full,k)=>full.toLowerCase()==='#evento'?(e.name||'nosso evento'):(({nome_convite:iv.title,nome_evento:e.name||'nosso evento',nome_do_contratante:state.rsvpContractor?.name||'',prazo_rsvp:e.rsvp_deadline?fmtDate(e.rsvp_deadline):'',nome_da_empresa:'Magda Féo Cerimoniais',data_evento:fmtDate(e.event_date),hora_evento:String(e.event_time||'').slice(0,5),link_rsvp:link})[k.toLowerCase()]||'')); return `Olá, ${iv.title}! Você está convidado(a) para ${e.name||'nosso evento'}. Data: ${fmtDate(e.event_date)}${e.event_time?' às '+String(e.event_time).slice(0,5):''}. Confirme sua presença pelo link: ${link} Esperamos você!`; } function rsvpWhatsappSvg(){return ``} function rsvpPage(){ const groups=rsvpInviteGroups(),open=new Set(state.rsvpOpenInvites||[]),selected=new Set(state.rsvpSelectedInvites||[]); const q=(state.rsvpSearch||'').toLowerCase().trim(); const digits=q.replace(/\D/g,''); const visible=groups.filter(iv=>{if(!q)return true;const text=[iv.title,...iv.guests.map(g=>g.name||'')].join(' ').toLowerCase();return text.includes(q)||(digits.length>0&&[iv.phone,...iv.guests.map(g=>g.phone)].some(phone=>phoneSearchMatch(phone,q)));}); const total=groups.length,pending=groups.filter(iv=>iv.guests.some(g=>(g.rsvp||'pendente')==='pendente')).length; const rows=visible.map(iv=>{ const pd=iv.guests.filter(g=>(g.rsvp||'pendente')==='pendente').length,ok=iv.guests.filter(g=>g.rsvp==='confirmado').length,no=iv.guests.filter(g=>g.rsvp==='recusado').length; const expanded=open.has(iv.key); return `
${esc(iv.phone||'Sem celular')}
! ${pd}✓ ${ok}× ${no}
${pd?'Envio pendente':'Respondido'}
${expanded?`
${iv.guests.map(g=>`
${esc(g.name)}
${rsvpStatusButtons(g)}
`).join('')}
`:''}
`; }).join(''); const contractor=state.rsvpContractor; return `

Envio Personalizado de Confirmação de Presença (RSVP)

Envie o link de confirmação e acompanhe cada convidado.

${state.guestLoadError?`
Não foi possível carregar a lista de convidados.
`:``}
Total: ${total} convitesEnvio pendente: ${pending}Selecionados: ${selected.size}
ConviteContatoSituaçãoStatusAções
${rows||'
Nenhum convite encontrado.
'}
`; } 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 occupied=state.guests.filter(g=>g.table_id).length;const totalSeats=state.tables.reduce((n,t)=>n+Number(t.capacity||0),0);const menu=state.tableMoreOpen?`
`:'';return `

Mapeamento de mesas

Organize os convidados por mesa de forma simples.
${menu}
${ico('search')}
${ico('users')} ${state.guests.length} convidados
${state.guests.filter(g=>String(g.rsvp||'').toLowerCase().includes('confirm')).length} confirmados${state.guests.filter(g=>!String(g.rsvp||'').toLowerCase().includes('confirm')&&!String(g.rsvp||'').toLowerCase().includes('recus')).length} pendentes${state.guests.filter(g=>String(g.rsvp||'').toLowerCase().includes('recus')).length} não comparecerão
${me?.role==='ASSESSORIA'?`${children} crianças · ${lapChildren} crianças de colo`:''}
${ico('table-2')} ${state.tables.length} mesas cadastradas${totalSeats} lugares · ${occupied} ocupados · ${un.length} convidados sem lugar
${un.length?`
${ico('user-round-x')}
${un.length} convidado${un.length===1?'':'s'} sem mesaAdicione uma mesa e use “Adicionar convidado” para organizar os lugares.
`:''}${!state.tables.length?`
${ico('table-2')}

Nenhuma mesa cadastrada

Crie a primeira mesa para começar a organizar os convidados deste evento.

`:`
${state.tables.map(t=>{const gs=state.guests.filter(g=>g.table_id===t.id),free=Math.max(0,Number(t.capacity||0)-gs.length);return `

${ico('table-2')} ${esc(t.name)}

${gs.length}/${t.capacity} lugares ocupados
${gs.map((g,i)=>`
${i+1}.${esc(g.name)}
`).join('')}${Array.from({length:free},(_,i)=>``).join('')}
${gs.length}/${t.capacity}
`}).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 groupsForPortaria=inviteGroups();const portariaCount=(g)=>groupsForPortaria.find(iv=>iv.guests.some(x=>x.id===g.id))?.guests.length||1;const q=(state.presenceSearch||'').toLowerCase(),filter=state.presenceFilter||'';const list=groupsForPortaria.filter(iv=>iv.guests.some(g=>(!q||String(g.name||'').toLowerCase().includes(q)||String(g.invite_name||'').toLowerCase().includes(q)||String(g.group_name||'').toLowerCase().includes(q))&&(!filter||(filter==='present'?g.checked_in:filter==='pending'?!g.checked_in&&g.rsvp==='confirmado':g.rsvp==='recusado')))).map(iv=>({...iv.guests.find(g=>(!q||String(g.name||'').toLowerCase().includes(q)||String(g.invite_name||'').toLowerCase().includes(q)||String(g.group_name||'').toLowerCase().includes(q))&&(!filter||(filter==='present'?g.checked_in:filter==='pending'?!g.checked_in&&g.rsvp==='confirmado':g.rsvp==='recusado')))||iv.guests[0],_portariaInvite:iv}));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=>{const members=g._portariaInvite?.guests||[g];const presentCount=members.filter(x=>Boolean(x.checked_in)).length;const allPresent=presentCount===members.length;const partlyPresent=presentCount>0&&!allPresent;return `
${esc(g._portariaInvite?.guests.map(x=>x.name).join(', ')||g.name)} · ${portariaCount(g)} pessoa${portariaCount(g)>1?'s':''}
${esc(g.group_name||'Convidados')}
${presentCount?``:g.rsvp!=='confirmado'?``:``}
`}).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 accessFilterRow(){return ``} function accessRows(users){return users.map(u=>{const events=u.role==='EQUIPE'?(u.event_ids||[]).map(id=>state.events.find(e=>e.id===id)?.name).filter(Boolean).join(', '):(state.events.find(e=>e.id===u.event_id)?.name||'Todos');const contact=u.role==='NOIVA'?(u.phone||'—'):(u.email||'—');return `${esc(u.name)}${esc(contact)}${esc(roleLabel[u.role]||u.role)}${esc(events||'—')}${u.active?'Ativo':'Inativo'}
${u.id!==me.id?``:''}
`}).join('')} function accessTable(users){return `
${accessFilterRow()}${accessRows(users)}
NomeWhatsApp / E-mailPerfilEventoStatusAcesso
`} function profilesPage(){const users=state.users.filter(u=>u.role!=='EQUIPE');return `
Dashboard › Assistentes

Assistentes e demais acessos

Contratantes, portaria e acessos administrativos ficam separados dos acessos operacionais da equipe.

Assessoria

Portal do Contratante

Portaria

Documentos para a equipe

Envie cerimonial ou recepção para um ou mais integrantes vinculados ao evento.

${accessTable(users)}` } function teamAccessPage(){const users=state.users.filter(u=>u.role==='EQUIPE');return `
Dashboard › Acessos da equipe

Acessos da equipe

Crie usuários individuais da equipe e determine exatamente em quais eventos cada pessoa pode trabalhar.
Equipe por evento
Estes acessos são separados de Contratante, Portaria e Administrador. Cada integrante visualiza somente os eventos vinculados ao seu usuário.

Documentos para a equipe

Envie cerimonial ou recepção para um ou mais integrantes vinculados ao evento.

${accessTable(users)}` } window.filterAccessTables=function(){document.querySelectorAll('.access-table').forEach(table=>{const filters={};table.querySelectorAll('[data-access-filter]').forEach(el=>filters[el.dataset.accessFilter]=String(el.value||'').trim().toLowerCase());table.querySelectorAll('.access-user-row').forEach(row=>{const ok=Object.entries(filters).every(([k,v])=>!v||String(row.dataset['f'+k.charAt(0).toUpperCase()+k.slice(1)]||row.getAttribute('data-f-'+k)||'').includes(v));row.style.display=ok?'':'none'})})} function modal(){if(!state.modal)return '';const m=state.modal; if(m.type==='table-filter'){const groups=[...new Set(state.guests.map(g=>g.group_name).filter(Boolean))].sort(),isAdmin=me?.role==='ASSESSORIA',f=state.tableFilters||{};return ``;} if(m.type==='share-tables'){const report=location.origin+`/tables-print.html?eventId=${encodeURIComponent(state.event.id)}`,msg=`Olá! Segue o link para visualizar o mapeamento de mesas do evento ${state.event?.name||''}:\n\n${report}`,tab=m.tab||'whatsapp',subject=`Mapeamento de mesas - ${state.event?.name||''}`;return ``;} if(m.type==='delete-all-event-guests'){return ``;} if(m.type==='rsvp-send-confirm'){return ``;} if(m.type==='rsvp-whatsapp'){return ``;} if(m.type==='rsvp-template-editor'){ const opts=rsvpTemplatesCache.map(t=>``).join(''); return ``; } if(m.type==='rsvp-contractor'){return ``;} if(m.type==='rsvp-batch'){return ``;} if(m.type==='confirm-delete')return ``; if(m.type==='install')return ``; if(m.type==='table-guests'){const t=state.tables.find(x=>x.id===m.tableId);if(!t)return '';const current=state.guests.filter(g=>g.table_id===t.id),available=state.guests.filter(g=>!g.table_id||g.table_id===t.id);return ``;}if(m.type==='table-create')return ``;if(m.type==='tables-create')return ``;if(m.type==='confirm-table-delete'){const t=state.tables.find(x=>x.id===m.tableId);const count=state.guests.filter(g=>g.table_id===m.tableId).length;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 facePrimary=iv.guests.find(g=>g.face_enabled)||iv.guests[0];const faceOthers=iv.guests.filter(g=>g.id!==facePrimary?.id);const faceCard=(g,label,isPrimary=false)=>`
${g?.face_enabled?`
${initials(label)}
`:`
${initials(label)}
`}
${esc(label)}${isPrimary?'Face principal do convite — esta identificação será exibida na portaria.':''}${g?.face_enabled?'Face cadastrada':'Face não cadastrada'}
${g?.face_enabled?'A imagem armazenada será usada como referência na portaria.':'Envie uma foto nítida ou use a câmera para cadastrar.'}
${g?``:''}
`;const facePane=`
Reconhecimento facial
${facePrimary?faceCard(facePrimary,iv.title,true):''}${faceOthers.length?`
Faces adicionais dos convidados (opcional)
${faceOthers.map(g=>faceCard(g,g.name,false)).join('')}`:''}
Para a entrada, a face principal identifica o nome do convite. Os demais convidados podem ter faces próprias opcionalmente.
`;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;const tablesOpts=state.tables.map(t=>``).join('');return ``} if(m.type==='import-guests')return ``;if(m.type==='task')return ``;if(m.type==='supplier')return ``;if(m.type==='confirm-profile-delete')return ``;if(m.type==='password')return ``;if(m.type==='team-access')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'||me.role==='SUPERVISAO'; const loadArray=async(url,key,label)=>{try{const v=await api(url);if(!Array.isArray(v))throw new Error('Formato inválido');saveLastGood(key+':'+id,v);return v}catch(err){const old=loadLastGood(key+':'+id);if(Array.isArray(old)){console.warn('[V117] Mantendo último estado válido de '+label,err);return old}throw new Error('Não foi possível carregar '+label+' agora. Os dados não foram apagados; tente novamente em instantes.')}}; // Convidados e mesas são críticos. Cada um tem fallback próprio para não bloquear o evento inteiro. const [guestResult,tableResult]=await Promise.allSettled([loadArray(`/events/${id}/guests`,'guests','os convidados'),loadArray(`/events/${id}/tables`,'tables','as mesas')]); state.guestLoadError=guestResult.status==='rejected'?(guestResult.reason?.message||'Não foi possível consultar convidados no servidor.'):''; state.guests=guestResult.status==='fulfilled'?guestResult.value:[]; state.tables=tableResult.status==='fulfilled'?tableResult.value:[]; if(guestResult.status==='rejected')console.error('[V179] Falha ao carregar convidados do evento '+id,guestResult.reason); if(tableResult.status==='rejected')console.error('[V179] Falha ao carregar mesas do evento '+id,tableResult.reason); try{const settings=await api(`/events/${id}/rsvp-settings`);state.event.rsvp_deadline=settings.deadline||''}catch(err){console.warn('Prazo RSVP indisponível',err);state.event.rsvp_deadline=''} try{await loadRsvpDispatches()}catch(err){console.warn('Estados RSVP indisponíveis',err);rsvpDispatchCache={}} try{state.rsvpContractor=await api(`/events/${id}/contractor-contact`)}catch(err){console.warn('[V119] Falha ao carregar contato do contratante deste evento',err);state.rsvpContractor=null} if(admin){ const defs=[['tasks',`/events/${id}/tasks`,'tarefas'],['briefings',`/events/${id}/briefing`,'briefings'],['scripts',`/events/${id}/scripts`,'roteiros'],['attachments',`/events/${id}/attachments`,'anexos'],['eventSuppliers',`/events/${id}/suppliers`,'fornecedores'],['eventServices',`/events/${id}/services`,'serviços']]; const settled=await Promise.allSettled(defs.map(x=>api(x[1]))); settled.forEach((res,i)=>{const [key,,label]=defs[i];if(res.status==='fulfilled'&&Array.isArray(res.value)){state[key]=res.value;saveLastGood(key+':'+id,res.value)}else{const old=loadLastGood(key+':'+id);if(Array.isArray(old))state[key]=old;console.warn('[V117] Falha não crítica em '+label,res.reason)}}); try{const note=await api(`/events/${id}/notes/general`);if(note&&typeof note==='object'&&!Array.isArray(note))state.eventNote=note}catch{} try{const sp=await api('/suppliers');if(Array.isArray(sp))state.suppliers=sp}catch{} }else{state.tasks=[];state.briefings=[];state.scripts=[];state.attachments=[];state.eventSuppliers=[];state.eventServices=[];state.eventNote={content:''};state.suppliers=[]} } async function openEvent(id){state.rsvpContractor=null;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'||v==='team-access')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)} document.addEventListener('click',function(ev){ const closeBtn=ev.target.closest('.side.noiva-side [data-a="toggle-side"]'); if(closeBtn && window.innerWidth<=900){ev.preventDefault();ev.stopPropagation();closeMobileMenu()} },true); 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==='presence-report-open172'){state.reportReturn172={view:state.view,guestTab:state.guestTab,eventTab:state.eventTab};state.reportMode172='invite';state.view='presence-report172';render();return}if(a==='presence-report-back172'){state.view=state.reportReturn172?.view||'event';state.guestTab=state.reportReturn172?.guestTab||'checkin';state.eventTab=state.reportReturn172?.eventTab||'guests';render();return}if(a==='presence-report-privacy172'){state.reportPrivacy172=!state.reportPrivacy172;render();return}if(a==='presence-report-print172'){window.print();return} if(me?.role==='SUPERVISAO' && (a==='delete-guest-v158'||a==='delete-invite-v158')){window.showAppError?.('Acesso somente para consulta.','Ação bloqueada');return;}if(me?.role==='SUPERVISAO' && !(state.view==='event'&&state.eventTab==='script') && /^(save|add|new|create|edit|delete|remove|duplicate|confirm|send|upload|import|assign|update|set|reset|rsvp-|checkin-|mark-|change-|generate|share-|guest-|face-|table-|invite-add|open-guest-edit)/.test(a)){ev.preventDefault();window.showAppError?.(new Error('Acesso somente para consulta. Alterações são permitidas apenas no Cerimonial.'),'Ação bloqueada');return;} if(a==='guest-actions-v159'){state.guestActionsOpen159=state.guestActionsOpen159===b.dataset.id?null:b.dataset.id;render();return;} 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(me?.role==='SUPERVISAO' && /^(save|add|new|create|edit|delete|remove|duplicate|confirm|send|upload|import|assign|update|set|reset|rsvp-|checkin-|mark-|change-|generate|share-)/.test(a) && !(state.view==='event'&&state.eventTab==='script'&&/^(new-script|save-script|remove-script|add-script-section|remove-script-section|duplicate-script|duplicate-script-section|share-script)$/.test(a))){window.showAppError?.('Esta ação está disponível somente no Cerimonial.','Acesso de consulta');return;}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==='rsvp-toggle-invite'){const k=b.dataset.key;state.rsvpOpenInvites=state.rsvpOpenInvites||[];state.rsvpOpenInvites=state.rsvpOpenInvites.includes(k)?state.rsvpOpenInvites.filter(x=>x!==k):[...state.rsvpOpenInvites,k];render()} else if(a==='rsvp-select-invite'){const k=b.dataset.key;state.rsvpSelectedInvites=state.rsvpSelectedInvites||[];state.rsvpSelectedInvites=b.checked?[...new Set([...state.rsvpSelectedInvites,k])]:state.rsvpSelectedInvites.filter(x=>x!==k);render()} else if(a==='rsvp-set-status'){await api(`/events/${state.event.id}/guests/${b.dataset.id}`,{method:'PATCH',body:JSON.stringify({rsvp:b.dataset.status})});const g=state.guests.find(x=>x.id===b.dataset.id);if(g)g.rsvp=b.dataset.status;render();show('Status atualizado.')} else if(a==='rsvp-whatsapp-modal'){const iv=rsvpInviteGroups().find(x=>x.key===b.dataset.key);if(!iv)return;await loadRsvpTemplates();state.modal={type:'rsvp-whatsapp',key:iv.key,title:iv.title,phone:iv.phone||'',message:rsvpMessageFor(iv)};render()} else if(a==='rsvp-open-whatsapp'){const phone=normalizeBrazilWhatsApp($('#rsvpWhatsPhone')?.value||''),message=$('#rsvpWhatsMessage')?.value||'';if(!phone)return show('Informe o WhatsApp do convite.');const key=state.modal?.key;const opened=window.open(`https://wa.me/${phone}?text=${encodeURIComponent(message)}`,'_blank');if(!opened)return show('Permita a abertura do WhatsApp e tente novamente.');await api(`/events/${state.event.id}/rsvp-dispatches`,{method:'POST',body:JSON.stringify({inviteKey:key,action:'initiated',phone})});await loadRsvpDispatches();state.modal={type:'rsvp-send-confirm',key};render()} else if(a==='rsvp-confirm-manual'){const key=state.modal?.key;if(!key)return;await api(`/events/${state.event.id}/rsvp-dispatches`,{method:'POST',body:JSON.stringify({inviteKey:key,action:'confirm_manual'})});await loadRsvpDispatches();state.modal=null;render();show('Envio confirmado manualmente.');} else if(a==='rsvp-refresh-dispatch'){await loadRsvpDispatches();render();show('Status dos envios atualizado.');} else if(a==='rsvp-send-official'){const key=b.dataset.key;if(!confirm('Enviar modelo aprovado pela API oficial? O status será atualizado apenas quando a Meta confirmar o envio.'))return;await api(`/events/${state.event.id}/rsvp-send-official`,{method:'POST',body:JSON.stringify({inviteKey:key})});await loadRsvpDispatches();render();show('Solicitação aceita pela API. Aguardando webhook de envio.');} else if(a==='rsvp-save-deadline'){const deadline=$('#rsvpDeadline')?.value||'';await api(`/events/${state.event.id}/rsvp-settings`,{method:'PATCH',body:JSON.stringify({deadline})});state.event.rsvp_deadline=deadline;render();show('Prazo RSVP salvo para este evento.');} else if(a==='rsvp-contractor-modal'){state.modal={type:'rsvp-contractor',data:state.rsvpContractor||{}};render()} else if(a==='rsvp-save-contractor'){const data=await api(`/events/${state.event.id}/contractor-contact`,{method:'PATCH',body:JSON.stringify({name:$('#rsvpContractorName')?.value||'',phone:$('#rsvpContractorPhone')?.value||''})});state.rsvpContractor=data;state.modal=null;render();show('Contato do contratante salvo.')} else if(a==='rsvp-build-batch'){await loadRsvpTemplates();const groups=rsvpInviteGroups();let items=groups;if(state.rsvpBatchMode==='pending')items=groups.filter(iv=>rsvpDispatchCache[iv.key]?.status!=='enviado');if(state.rsvpBatchMode==='selected'){const ss=new Set(state.rsvpSelectedInvites||[]);items=groups.filter(iv=>ss.has(iv.key))}items=items.filter(iv=>String(iv.phone||'').replace(/\D/g,''));if(!items.length)return show('Nenhum convite com WhatsApp encontrado para este filtro.');state.modal={type:'rsvp-batch',items:items.map(iv=>({key:iv.key,title:iv.title,phone:iv.phone,message:rsvpMessageFor(iv)})),index:0};render()} else if(a==='rsvp-batch-confirm'){const m=state.modal;if(m?.type!=='rsvp-batch'||!m.waitingKey)return;await api(`/events/${state.event.id}/rsvp-dispatches`,{method:'POST',body:JSON.stringify({inviteKey:m.waitingKey,action:'confirm_manual'})});m.waitingKey=null;await loadRsvpDispatches();render();show('Envio confirmado manualmente.');} else if(a==='rsvp-batch-next'){const m=state.modal;if(m?.type!=='rsvp-batch')return;if(m.waitingKey)return show('Confirme o envio anterior ou feche a fila para deixá-lo como iniciado.');const item=m.items[m.index];if(!item)return;const phone=normalizeBrazilWhatsApp(item.phone||'');const opened=window.open(`https://wa.me/${phone}?text=${encodeURIComponent(item.message)}`,'_blank');if(!opened)return show('Permita o pop-up e tente novamente.');await api(`/events/${state.event.id}/rsvp-dispatches`,{method:'POST',body:JSON.stringify({inviteKey:item.key,action:'initiated',phone})});await loadRsvpDispatches();m.index++;m.waitingKey=item.key;render()} else if(a==='retry-event-guests'){await loadEventData();render();if(state.guestLoadError)show('Falha ao carregar convidados. Verifique o servidor.');else show('Lista atualizada.')} else if(a==='delete-all-event-guests'){if(state.guestLoadError)return show('Exclusão bloqueada: a lista não foi carregada do servidor.');if(me?.role!=='ASSESSORIA')return show('Apenas a assessoria pode excluir toda a lista.');if(!state.event)return;state.modal={type:'delete-all-event-guests',eventId:state.event.id,eventName:state.event.name,count:state.guests.length};render()} else if(a==='confirm-delete-all-event-guests'){if(state.guestLoadError)return show('Exclusão bloqueada: não foi possível confirmar os dados do servidor.');const m=state.modal;if(m?.type!=='delete-all-event-guests'||m.eventId!==state.event?.id)return;const entered=$('#deleteAllEventConfirmName')?.value||'';if(entered!==m.eventName)return show('Digite exatamente o nome do evento para confirmar.');const result=await api(`/events/${m.eventId}/guests/delete-all`,{method:'POST',body:JSON.stringify({eventName:m.eventName,expectedCount:m.count})});state.guests=await api(`/events/${m.eventId}/guests`);state.selectedGuests=[];state.modal=null;render();show(`${result.deleted} convidados excluídos deste evento.`)} else if(a==='rsvp-edit-template'){await loadRsvpTemplates();const tpl=rsvpTemplatesCache.find(x=>Number(x.is_default)===1)||rsvpTemplatesCache[0];state.modal={type:'rsvp-template-editor',id:tpl?.id||'',name:tpl?.name||'',body:tpl?.body||''};render()} else if(a==='rsvp-template-new'){state.modal={type:'rsvp-template-editor',id:'',name:'',body:''};render()} else if(a==='rsvp-template-save'){const name=$('#rsvpTplName')?.value.trim(),body=$('#rsvpTplBody')?.value||'';if(!name||!body.trim())return show('Informe nome e mensagem.');const id=state.modal?.id;await api('/message-templates'+(id?'/'+id:''),{method:id?'PATCH':'POST',body:JSON.stringify({name,body})});await loadRsvpTemplates();const chosen=rsvpTemplatesCache.find(x=>x.name===name);state.modal={type:'rsvp-template-editor',id:chosen?.id||'',name,body};render();show('Modelo salvo no sistema.')} else if(a==='rsvp-template-default'){const id=state.modal?.id;if(!id)return;await api('/message-templates/default',{method:'POST',body:JSON.stringify({id})});await loadRsvpTemplates();state.modal={...state.modal};render();show('Modelo padrão atualizado.')} else if(a==='rsvp-template-delete'){const id=state.modal?.id;if(!id||!confirm('Excluir este modelo de mensagem?'))return;await api('/message-templates/'+id,{method:'DELETE'});await loadRsvpTemplates();state.modal={type:'rsvp-template-editor',id:'',name:'',body:''};render();show('Modelo excluído.')} else if(a==='delete-guest-v158'||a==='delete-invite-v158'){ if(me.role!=='ASSESSORIA'){window.showAppError?.('Acesso somente para consulta.','Ação bloqueada');return;} const iv=a==='delete-invite-v158'?inviteGroups().find(x=>x.key===b.dataset.key):null; const ids=iv?iv.guests.map(x=>x.id):[b.dataset.id]; if(!ids.length)return; const label=iv?`o convite \"${iv.title}\" e seus ${ids.length} integrante(s)`:`o convidado \"${state.guests.find(x=>x.id===ids[0])?.name||''}\"`; if(!confirm(`Excluir definitivamente ${label}? Esta operação não pode ser desfeita.`))return; try{await api(`/events/${state.event.id}/guests/bulk-delete`,{method:'POST',body:JSON.stringify({ids})});await loadEventData();render();show(iv?'Convite e integrantes excluídos.':'Convidado excluído.')}catch(err){window.showAppError?.(err,'Falha ao excluir');} }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;const origin=state.modal?.type==='invite'?inviteGroups().find(x=>x.key===state.modal.key):null;state.modal={type:'guest',parentInvite:origin?{id:origin.guests[0]?.invite_id||'',anchorGuestId:origin.guests[0]?.id||'',memberIds:origin.guests.map(x=>x.id),name:origin.title,phone:origin.phone,group:origin.group}:null};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:state.modal?.parentInvite?.name||state.modal?.portariaInviteName||$('#ginviteName')?.value||$('#gname').value,inviteId:state.modal?.parentInvite?.id||state.modal?.portariaInviteId||undefined,anchorGuestId:state.modal?.parentInvite?.anchorGuestId||undefined,memberIds:state.modal?.parentInvite?.memberIds||undefined,phone:state.modal?.parentInvite?.phone||state.modal?.portariaPhone||$('#gphone').value,groupName:state.modal?.parentInvite?.group||state.modal?.portariaGroup||$('#ggroup').value,ageGroup:$('#gageGroup')?.value||null,rsvp:$('#grsvp').value,gender:$('#ggender')?.value||null,fareType:$('#gfare')?.value||null,rg:$('#grg')?.value||null,cpf:$('#gcpf')?.value||null,tableId:$('#gtable')?.value||null};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==='guest-rsvp-pick'){const input=$('#grsvp');if(input)input.value=b.dataset.status;document.querySelectorAll('.vip-rsvp-large .vip-rsvp-icon').forEach(x=>x.classList.toggle('active',x.dataset.status===b.dataset.status))}else if(a==='guest-choice'){const field=document.getElementById(b.dataset.field);if(field)field.value=b.dataset.value;b.parentElement.querySelectorAll('button').forEach(x=>x.classList.toggle('active',x===b))}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==='table-more'){state.tableMoreOpen=!state.tableMoreOpen;render()}else if(a==='share-tables'){state.tableMoreOpen=false;state.modal={type:'share-tables',tab:'whatsapp'};render()}else if(a==='share-table-tab'){if(state.modal?.type==='share-tables'){state.modal.tab=b.dataset.tab||'whatsapp';render()}}else if(a==='send-table-whatsapp'){const phone=($('#shareTablePhone')?.value||'').replace(/\D/g,''),msg=$('#shareTableMessage')?.value||'';window.open(`https://wa.me/${phone?('55'+phone.replace(/^55/,'')):''}?text=${encodeURIComponent(msg)}`,'_blank')}else if(a==='send-table-email'){const to=$('#shareTableEmail')?.value?.trim()||'',subject=$('#shareTableSubject')?.value||'',msg=$('#shareTableEmailMessage')?.value||'';location.href=`mailto:${encodeURIComponent(to)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(msg)}`}else if(a==='copy-table-link'){const link=$('#shareTableLink')?.value||location.origin+`/tables-print.html?eventId=${encodeURIComponent(state.event.id)}`;try{await navigator.clipboard.writeText(link);show('Link do mapeamento copiado.')}catch{const input=$('#shareTableLink');if(input){input.removeAttribute('readonly');input.select();document.execCommand('copy');input.setAttribute('readonly','');show('Link do mapeamento copiado.')}else prompt('Copie o link:',link)}}else if(a==='print-tables'){state.tableMoreOpen=false;window.open(`/tables-print.html?eventId=${encodeURIComponent(state.event.id)}`,'_blank');render()}else if(a==='table-filter'){state.modal={type:'table-filter'};render()}else if(a==='apply-table-filter'){state.tableFilters={status:$('#tfStatus')?.value||'all',seats:$('#tfSeats')?.value||'all',age:$('#tfAge')?.value||'all',group:$('#tfGroup')?.value||'all'};state.modal=null;render();applyTableFilters()}else if(a==='clear-table-filter'){state.tableFilters={status:'all',seats:'all',age:'all',group:'all'};state.modal=null;render();applyTableFilters()}else if(a==='delete-table'){state.modal={type:'confirm-table-delete',tableId:b.dataset.id};render()}else if(a==='confirm-delete-table'){await api(`/events/${state.event.id}/tables/${b.dataset.id}`,{method:'DELETE'});state.modal=null;await loadEventData();render();show('Mesa excluída. Os convidados vinculados ficaram sem mesa.')}else if(a==='table-add-guests'){state.modal={type:'table-guests',tableId:b.dataset.id};render()}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-guests'){const tid=b.dataset.id,t=state.tables.find(x=>x.id===tid);const selected=[...document.querySelectorAll('.table-guest-check:checked')].map(x=>x.value);if(selected.length>Number(t?.capacity||0))return show(`Esta mesa comporta no máximo ${t.capacity} convidados.`);const candidates=state.guests.filter(g=>!g.table_id||g.table_id===tid);for(const g of candidates){const should=selected.includes(g.id),is=g.table_id===tid;if(should!==is)await api(`/events/${state.event.id}/guests/${g.id}/table`,{method:'PATCH',body:JSON.stringify({tableId:should?tid:null})});}state.modal=null;await loadEventData();render();show('Convidados da mesa atualizados.')}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.');window.locatePortariaCode(code)}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-team-access'){state.modal={type:'team-access'};render()}else if(a==='clear-access-filters'){document.querySelectorAll('[data-access-filter]').forEach(x=>x.value='');window.filterAccessTables();return}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==='remove-profile'){const u=state.users.find(x=>x.id===b.dataset.id);if(!u)return;state.modal={type:'confirm-profile-delete',user:u};render()}else if(a==='confirm-profile-delete'){await api(`/users/${b.dataset.id}`,{method:'DELETE'});state.users=await api('/users');state.modal=null;render();show('Perfil removido com sucesso.')}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-team-access'){const name=$('#uname')?.value.trim(),email=$('#uemail')?.value.trim(),password=$('#upassword')?.value||'';const eventIds=[...document.querySelectorAll('.team-event-check:checked')].map(x=>x.value);if(!name||!email)return show('Informe nome e e-mail.');if(!eventIds.length)return show('Selecione pelo menos um evento para a equipe.');const r=await api('/users',{method:'POST',body:JSON.stringify({name,email,role:'EQUIPE',eventIds,password:password||undefined})});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==='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 original=photo.files[0];if(!original)return;try{if(!original.type.startsWith('image/'))throw Error('Selecione uma imagem JPG, PNG, WEBP ou GIF.');if(original.size>20*1024*1024)throw Error('A imagem é muito grande. Selecione uma foto de até 20 MB.');let f=original;try{const img=await createImageBitmap(original);const max=1600,scale=Math.min(1,max/Math.max(img.width,img.height));const w=Math.max(1,Math.round(img.width*scale)),h=Math.max(1,Math.round(img.height*scale));const cv=document.createElement('canvas');cv.width=w;cv.height=h;cv.getContext('2d').drawImage(img,0,0,w,h);const blob=await new Promise((resolve,reject)=>cv.toBlob(b=>b?resolve(b):reject(Error('Falha ao preparar a imagem.')),'image/jpeg',.84));if(img.close)img.close();f=new File([blob],'evento.jpg',{type:'image/jpeg'});}catch(compressErr){if(original.size>5*1024*1024)throw Error('Não foi possível otimizar esta foto. Tente outra imagem ou uma foto menor que 5 MB.');}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 com sucesso.');}catch(err){if(window.showAppError)window.showAppError(err,'Não foi possível alterar a foto');else show(err.message||String(err));}finally{photo.value='';}};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())||(gs.value.replace(/\D/g,'').length>=3&&r.dataset.gname.replace(/\D/g,'').includes(gs.value.replace(/\D/g,''))))&&(!gf.value||r.dataset.rsvp===gf.value))?'':'none')};if(gs)gs.oninput=applyGuest;if(gf)gf.onchange=applyGuest;const rs=$('#rsvpSearch');if(rs)rs.oninput=()=>{state.rsvpSearch=rs.value;const pos=rs.selectionStart;render();setTimeout(()=>{const n=$('#rsvpSearch');if(n){n.focus();try{n.setSelectionRange(pos,pos)}catch{}}},0)};document.querySelectorAll('input[name=rsvpBatch]').forEach(r=>r.onchange=()=>{state.rsvpBatchMode=r.value;render()});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()}} document.addEventListener('input',e=>{if(e.target?.id==='tableGuestSearch'){const q=e.target.value.trim().toLowerCase();document.querySelectorAll('.table-guest-option').forEach(x=>x.style.display=!q||x.dataset.search.includes(q)?'grid':'none');}if(e.target?.classList?.contains('table-guest-check')){const t=state.tables.find(x=>x.id===state.modal?.tableId),n=document.querySelectorAll('.table-guest-check:checked').length,c=document.getElementById('tableGuestCounter');if(c)c.textContent=`${n} selecionados de ${t?.capacity||0}`;}}); 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)}}})} function applyTableFilters(){const q=(state.tableSearch||'').toLowerCase().trim(),f=state.tableFilters||{};document.querySelectorAll('.vip-table-card').forEach(card=>{const t=state.tables.find(x=>x.id===card.dataset.table),gs=state.guests.filter(g=>g.table_id===card.dataset.table),cap=Number(t?.capacity||0);const matchesQ=!q||(card.dataset.tableSearch||'').includes(q);const seats=(f.seats||'all')==='all'||(f.seats==='available'&&cap>0&&gs.length0&&gs.length===cap)||(f.seats==='exceeded'&&cap>0&&gs.length>cap)||(f.seats==='unknown'&&!cap);const status=(f.status||'all')==='all'||gs.some(g=>{const v=String(g.rsvp||'').toLowerCase();const k=v.includes('confirm')?'confirmado':v.includes('recus')?'recusado':'pendente';return k===f.status});const age=(f.age||'all')==='all'||gs.some(g=>tableAgeKey(g)===f.age);const group=(f.group||'all')==='all'||gs.some(g=>(g.group_name||'')===f.group);card.style.display=matchesQ&&seats&&status&&age&&group?'':'none'})} document.addEventListener('input',e=>{if(e.target?.id==='tableSearch'){state.tableSearch=e.target.value;applyTableFilters()}}); // V130: filtros das tabelas de acessos funcionam enquanto o usuário digita/seleciona. function runAccessColumnFilters(){ document.querySelectorAll('.access-table').forEach(table=>{ const filters={}; table.querySelectorAll('[data-access-filter]').forEach(el=>{filters[el.dataset.accessFilter]=String(el.value||'').trim().toLowerCase()}); table.querySelectorAll('.access-user-row').forEach(row=>{ const values={name:row.dataset.fName||'',contact:row.dataset.fContact||'',role:row.dataset.fRole||'',event:row.dataset.fEvent||'',status:row.dataset.fStatus||''}; const visible=Object.entries(filters).every(([key,value])=>!value||String(values[key]||'').toLowerCase().includes(value)); row.hidden=!visible; row.style.display=visible?'':'none'; }); }); } window.filterAccessTables=runAccessColumnFilters; document.addEventListener('input',e=>{if(e.target?.matches?.('[data-access-filter]'))runAccessColumnFilters()}); document.addEventListener('change',e=>{if(e.target?.matches?.('[data-access-filter]'))runAccessColumnFilters()}); async function boot(){state.sideCollapsed=localStorage.getItem('sideCollapsed')==='1';if(!me){render();return}try{const events=validArray(await api('/events'),'eventos');state.events=events;saveLastGood('events',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)||null;if(state.event)await loadEventData();state.view='event';state.eventTab='guests'}render()}catch(err){console.error('[V114] Falha de inicialização preservando sessão e último estado válido:',err);const cached=loadLastGood('events');if(cached)state.events=cached;ensureStateArrays();render();if(err?.status===401){token='';me=null;localStorage.removeItem('token');localStorage.removeItem('me');render();return}setTimeout(()=>window.showAppError?.(new Error('Houve uma instabilidade temporária de conexão. Nenhum dado foi apagado. Tente novamente em alguns instantes.'),'Conexão temporariamente indisponível'),50)}} boot();