// Local do check-in — lado da producao. Orcamento 298, Fase 1. // // A validacao de raio da migration 038 so entra em acao se o evento tiver // coordenada. Sem esta tela ela fica inerte e o check-in aceita qualquer lugar. // // COMO A PRODUCAO INFORMA O PONTO // 1) cola o link do Google Maps do local (jeito mais comum no dia a dia), ou // 2) cola "lat, lng" direto, ou // 3) toca em "Usar minha localizacao" quando ja esta no local montando. // Nao usamos API paga de geocodificacao: para uma acao por vez, colar o link do // Maps resolve e nao cria custo recorrente nem chave para gerenciar. const RAIO_PADRAO_M = 300; const RAIO_MIN_M = 50; const RAIO_MAX_M = 5000; // Aceita os formatos que o Google Maps produz no compartilhamento: // https://maps.google.com/...@-3.7420,-38.4820,17z // https://maps.google.com/?q=-3.7420,-38.4820 // ...!3d-3.7420!4d-38.4820 // "-3.7420, -38.4820" (colado na mao) function extrairCoordenadas(texto) { const t = String(texto || '').trim(); if (!t) return null; const padroes = [ /@(-?\d+\.\d+),\s*(-?\d+\.\d+)/, /[?&]q=(-?\d+\.\d+),\s*(-?\d+\.\d+)/, /!3d(-?\d+\.\d+)!4d(-?\d+\.\d+)/, /^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/, ]; for (const re of padroes) { const m = t.match(re); if (!m) continue; const lat = parseFloat(m[1]); const lng = parseFloat(m[2]); // Fora destes limites nao e coordenada valida — normalmente e um link // encurtado (maps.app.goo.gl), que nao carrega o ponto. if (Math.abs(lat) <= 90 && Math.abs(lng) <= 180) return { lat, lng }; } return null; } function LocalCheckInCard({ event, showToast }) { const salvo = (event && event.local_lat != null && event.local_lng != null) ? { lat: Number(event.local_lat), lng: Number(event.local_lng) } : null; // O campo abria vazio mesmo com ponto salvo — o link colado nao e guardado, // so as coordenadas que saem dele. Campo em branco le-se como "nao salvou", e // a producao redigitava o link do Maps toda vez que abria a acao. Mostrar a // coordenada salva deixa evidente que o ponto esta la, e ela continua podendo // colar um link novo por cima para trocar de lugar. const [entrada, setEntrada] = React.useState(salvo ? `${salvo.lat}, ${salvo.lng}` : ''); const [raio, setRaio] = React.useState(String((event && event.raio_checkin_metros) || RAIO_PADRAO_M)); const [coord, setCoord] = React.useState(salvo); const [salvando, setSalvando] = React.useState(false); const [buscandoGps, setBuscandoGps] = React.useState(false); const [aviso, setAviso] = React.useState(''); const [buscandoEnd, setBuscandoEnd] = React.useState(false); // Antes, um texto que o app nao entendia deixava a coordenada ANTERIOR no // lugar: a producao colava um link novo, salvava, lia "Local do check-in // salvo!" e o ponto continuava o de antes. Ficava impossivel corrigir um // local errado — o botao dizia que gravou, e nada mudava. Agora um texto que // nao vira coordenada zera o ponto e o Salvar fica bloqueado, com o motivo // escrito na tela. const aplicarEntrada = (valor) => { setEntrada(valor); const t = String(valor || '').trim(); if (!t) { setCoord(null); setAviso(''); return; } const c = extrairCoordenadas(t); if (c) { setCoord(c); setAviso(''); return; } setCoord(null); // O app do Google Maps no celular compartilha SEMPRE em link curto, e o // ponto nao viaja dentro dele — so o servidor do Google sabe para onde // aponta. Dizer isso e mostrar a saida vale mais do que "link invalido". setAviso(/maps\.app\.goo\.gl|goo\.gl\/maps/i.test(t) ? 'Esse é um link curto — o endereço não vem dentro dele. Escreva o nome do local no campo e toque em "Buscar endereço", ou abra o link no navegador e copie o link completo da barra de endereço.' : 'Não reconheci um ponto nesse texto. Escreva o nome do local e toque em "Buscar endereço", cole o link completo do Google Maps, ou digite "latitude, longitude".'); }; // Busca o ponto pelo nome do lugar ("Shopping Parangaba, Fortaleza"). Usa o // mesmo servico gratuito que o app do promotor ja usa para traduzir a // coordenada do check-in em endereco — sem chave e sem custo por consulta. const buscarEndereco = async () => { const termo = String(entrada || '').trim(); if (termo.length < 3) { setAviso('Escreva o nome do local antes de buscar. Ex: Shopping Parangaba, Fortaleza'); return; } setBuscandoEnd(true); setAviso(''); try { const resp = await fetch( 'https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&countrycodes=br&accept-language=pt-BR&q=' + encodeURIComponent(termo), { headers: { Accept: 'application/json' } }, ); const json = resp.ok ? await resp.json() : null; const achado = Array.isArray(json) && json[0]; if (!achado) { setAviso('Não encontrei esse endereço. Tente com a cidade junto (ex: "Shopping Parangaba, Fortaleza") ou use "Usar minha localização" quando estiver no local.'); return; } const lat = parseFloat(achado.lat); const lng = parseFloat(achado.lon); setCoord({ lat, lng }); setEntrada(lat.toFixed(7) + ', ' + lng.toFixed(7)); showToast('Encontrei: ' + String(achado.display_name).slice(0, 70) + '. Confira no mapa e salve.'); } catch (err) { console.error('[LocalCheckInCard.buscarEndereco] error:', err); setAviso('A busca de endereço não respondeu. Tente de novo ou informe "latitude, longitude".'); } setBuscandoEnd(false); }; const usarMinhaLocalizacao = async () => { setBuscandoGps(true); try { const p = await obterPosicao(); setCoord({ lat: p.lat, lng: p.lng }); setEntrada(p.lat.toFixed(7) + ', ' + p.lng.toFixed(7)); showToast('Localização capturada. Confira e salve.'); } catch (err) { showToast(err.message, 'danger'); } setBuscandoGps(false); }; const salvar = async () => { const raioNum = parseInt(raio, 10); if (!coord) { showToast('Informe o ponto do local antes de salvar.', 'danger'); return; } if (!Number.isFinite(raioNum) || raioNum < RAIO_MIN_M || raioNum > RAIO_MAX_M) { showToast('O raio precisa ficar entre ' + RAIO_MIN_M + ' e ' + RAIO_MAX_M + ' metros.', 'danger'); return; } setSalvando(true); const { error } = await sb.from('events').update({ local_lat: coord.lat, local_lng: coord.lng, raio_checkin_metros: raioNum, }).eq('id', event.id); setSalvando(false); if (error) { console.error('[LocalCheckInCard.salvar] Supabase error:', error); showToast('Erro ao salvar o local do check-in.', 'danger'); return; } // Mantem o objeto em memoria coerente com o banco — mesmo padrao do // handleSalvarCoordenador, em Escalas.jsx. event.local_lat = coord.lat; event.local_lng = coord.lng; event.raio_checkin_metros = raioNum; showToast('Local do check-in salvo!'); }; const mapaUrl = coord ? 'https://www.google.com/maps?q=' + coord.lat + ',' + coord.lng : null; return (