// Cotações — Solicitação de Orçamento para vários fornecedores de uma vez // // Primeira metade do fluxo de Compras: // Evento -> Solicitar Orçamento -> Fornecedores respondem (link público) // -> Comparar valores -> Escolher fornecedor -> gera a Ordem de Compra. // // O fornecedor responde em oc.html, que fala com a Edge Function // 'oc-publica'. Aqui a equipe só dispara e compara. const COT_STATUS_VARIANT = { Pendente: 'default', Enviada: 'primary', Respondida: 'success', Recusada: 'danger', }; // Texto padronizado que vai pro fornecedor — o mesmo no WhatsApp e no e-mail. // Padronizar é metade do ganho: hoje cada pedido sai escrito de um jeito. function mensagemCotacao({ pedido, fornecedor, link }) { const dataEvento = pedido.evento_data ? new Date(pedido.evento_data + 'T00:00:00').toLocaleDateString('pt-BR') : null; const prazo = pedido.prazo_resposta ? new Date(pedido.prazo_resposta + 'T00:00:00').toLocaleDateString('pt-BR') : null; return [ `Olá, ${fornecedor.contato || fornecedor.nome}! Aqui é da Promoup.`, '', `Precisamos de um orçamento para: *${pedido.titulo}*`, pedido.evento_nome ? `Evento: ${pedido.evento_nome}${dataEvento ? ` — ${dataEvento}` : ''}` : '', pedido.evento_local ? `Local: ${pedido.evento_local}` : '', '', pedido.descricao || '', '', prazo ? `Precisamos da resposta até ${prazo}.` : '', '', 'Envie sua proposta por aqui (leva 1 minuto, é só preencher o valor):', link, '', `Referência: ${pedido.numero}`, ].join('\n').replace(/\n{3,}/g, '\n\n'); } // --------------------------------------------------------------------- // Modal de nova solicitação — chamado pelo módulo Fornecedores // (botão "Solicitar orçamento" com os fornecedores já selecionados) // e também pela própria tela de Cotações. // --------------------------------------------------------------------- function NovaCotacaoModal({ open, fornecedores = [], onClose, onCreated }) { const [events, setEvents] = React.useState([]); const [saving, setSaving] = React.useState(false); const [erro, setErro] = React.useState(null); const [resultado, setResultado] = React.useState(null); const vazio = { event_id: '', titulo: '', descricao: '', categoria: '', prazo_resposta: '' }; const [form, setForm] = React.useState(vazio); React.useEffect(() => { if (!open) return; setForm(vazio); setErro(null); setResultado(null); sb.from('events').select('id, name, event_date, client_name, location') .order('event_date', { ascending: false }).limit(200) .then(({ data }) => setEvents(data || [])); }, [open]); const criar = async () => { if (!form.titulo.trim()) { setErro('Descreva o que você precisa orçar.'); return; } if (!fornecedores.length) { setErro('Selecione pelo menos um fornecedor.'); return; } setSaving(true); setErro(null); const ev = events.find(e => String(e.id) === String(form.event_id)); const { data: pedido, error } = await sb.from('quote_requests').insert({ event_id: ev ? ev.id : null, evento_nome: ev ? ev.name : null, evento_data: ev ? ev.event_date : null, evento_local: ev ? ev.location : null, cliente_nome: ev ? ev.client_name : null, titulo: form.titulo.trim(), descricao: form.descricao.trim() || null, categoria: form.categoria || null, prazo_resposta: form.prazo_resposta || null, }).select().single(); if (error) { setSaving(false); console.error('[Cotacoes.criar] erro:', error); setErro('Não consegui criar a solicitação: ' + (error.message || 'erro desconhecido')); return; } // Uma linha por fornecedor: cada um recebe o próprio token/link const { data: linhas, error: errLinhas } = await sb.from('quote_request_suppliers').insert( fornecedores.map(f => ({ request_id: pedido.id, fornecedor_id: f.id, fornecedor_nome: f.nome, })) ).select(); if (errLinhas) { setSaving(false); console.error('[Cotacoes.criar] erro nos fornecedores:', errLinhas); setErro('A solicitação foi criada, mas não consegui vincular os fornecedores.'); return; } // Dispara no WhatsApp de quem tem telefone. Quem não tem entra na lista // de pendências para a equipe mandar por e-mail. const enviados = [], pendentes = []; for (const linha of linhas) { const f = fornecedores.find(x => x.id === linha.fornecedor_id); const link = publicDocUrl(linha.token); const texto = mensagemCotacao({ pedido, fornecedor: f, link }); const r = await notifyFornecedorWhatsApp({ fornecedor: f, text: texto }); if (r.ok) enviados.push(linha.id); else pendentes.push({ nome: f.nome, email: f.email, motivo: r.motivo, link, texto }); } if (enviados.length) { await sb.from('quote_request_suppliers') .update({ status: 'Enviada', sent_at: new Date().toISOString(), sent_via: 'whatsapp' }) .in('id', enviados); } setSaving(false); if (pendentes.length) { // Não fecha o modal: mostra quem ficou de fora com o link pronto setResultado({ pedido, enviados: enviados.length, pendentes }); } else { onCreated?.(`${pedido.numero} enviada para ${enviados.length} fornecedor(es) no WhatsApp.`); } }; const copiar = (txt) => navigator.clipboard?.writeText(txt); return ( {resultado ? (
{resultado.pedido.numero} criada. {resultado.enviados} fornecedor(es) receberam no WhatsApp.
Estes aqui não têm WhatsApp válido cadastrado. Mande o link por e-mail:
{resultado.pendentes.map(p => (
{p.nome}
{p.motivo}
abrirEmail({ to: p.email, subject: `Solicitação de orçamento — ${resultado.pedido.numero}`, body: p.texto })}> {p.email ? 'Abrir e-mail' : 'Sem e-mail'} copiar(p.link)}>Copiar link
))}
onCreated?.(`${resultado.pedido.numero} criada.`)}>Concluir
) : (
{fornecedores.length} fornecedor(es) selecionado(s)
{fornecedores.length === 0 ?
Volte e selecione os fornecedores na lista.
:
{fornecedores.map(f => {f.nome})}
}
setForm(f => ({ ...f, titulo: v }))} placeholder="Ex: Sonorização completa para 300 pessoas" />