photum/frontend/services/mapboxService.ts
NANDO9322 21987d221e feat(ux): melhorias de navegação, formulário e fluxo de eventos
Backend:
- Adiciona campo `contacts` (JSONB) na tabela `agendas` e atualiza lógica de criação.

Frontend:
- Adiciona campos dinâmicos de contato no formulário de Novo Evento.
- Otimiza busca do Mapbox priorizando a região selecionada (SP/MG).
- Implementa "Deep Linking" no Dashboard (abrir detalhes do evento direto via URL).
- Corrige "flicker" (piscada da lista) ao carregar detalhes via link permitindo carregamento suave.
- Adiciona botão "Aprovar" e fluxo de aprovação na visualização de detalhes.
- Corrige fluxo de edição (salvar retorna para detalhes sem recarregar a página).
- Corrige navegação dos botões "Voltar" em Detalhes e Logística para retornarem corretamente à lista/painel.
- Melhora layout do cabeçalho de detalhes (remove ID vazio e unifica títulos duplicados).
- Ajusta clique no Logo para forçar reset da navegação para o Painel.
2026-02-06 13:32:11 -03:00

166 lines
4.8 KiB
TypeScript

// Mapbox Geocoding Service
// Docs: https://docs.mapbox.com/api/search/geocoding/
export interface MapboxFeature {
id: string;
place_name: string;
center: [number, number]; // [longitude, latitude]
geometry: {
coordinates: [number, number];
};
context?: Array<{
id: string;
text: string;
short_code?: string;
}>;
place_type: string[];
text: string;
address?: string;
}
export interface MapboxResult {
description: string;
street: string;
number: string;
city: string;
state: string;
zip: string;
lat: number;
lng: number;
mapLink: string;
}
// Token do Mapbox configurado no arquivo .env.local
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_TOKEN || "";
/**
* Busca endereços usando a API de Geocoding do Mapbox
* @param query - Texto de busca (endereço, local, etc)
* @param country - Código do país (ex: 'br' para Brasil)
*/
export async function searchMapboxLocation(
query: string,
country: string = "br"
): Promise<MapboxResult[]> {
if (!MAPBOX_TOKEN || MAPBOX_TOKEN.startsWith("YOUR_")) {
console.warn(
"⚠️ Mapbox Token não configurado. Configure em services/mapboxService.ts"
);
return [];
}
try {
const encodedQuery = encodeURIComponent(query);
let url =
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodedQuery}.json?` +
`access_token=${MAPBOX_TOKEN}&` +
`country=${country}&` +
`language=pt&` +
`limit=10`;
// Add proximity bias based on region
const region = localStorage.getItem("photum_selected_region");
if (region === "MG") {
// Belo Horizonteish center
url += `&proximity=-43.9378,-19.9208`;
} else {
// São Pauloish center (Default)
url += `&proximity=-46.6333,-23.5505`;
}
console.log("🔍 Buscando endereço:", query);
const response = await fetch(url);
if (!response.ok) {
console.error("❌ Erro na API Mapbox:", response.statusText);
throw new Error(`Mapbox API error: ${response.statusText}`);
}
const data = await response.json();
console.log("✅ Resultados encontrados:", data.features?.length || 0);
if (!data.features || data.features.length === 0) {
return [];
}
return data.features.map((feature: MapboxFeature) => {
// Extrair informações do contexto
const context = feature.context || [];
const place = context.find((c) => c.id.startsWith("place"));
const region = context.find((c) => c.id.startsWith("region"));
const postcode = context.find((c) => c.id.startsWith("postcode"));
// Extrair número do endereço
const addressMatch =
feature.address || feature.text.match(/\d+/)?.[0] || "";
return {
description: feature.place_name,
street: feature.text,
number: addressMatch,
city: place?.text || "",
state: region?.short_code?.replace("BR-", "") || region?.text || "",
zip: postcode?.text || "",
lat: feature.center[1],
lng: feature.center[0],
mapLink: `https://www.google.com/maps/search/?api=1&query=${feature.center[1]},${feature.center[0]}`,
};
});
} catch (error) {
console.error("Erro ao buscar localização no Mapbox:", error);
return [];
}
}
/**
* Busca reversa: converte coordenadas em endereço
* Retorna o endereço completo baseado nas coordenadas do pin
*/
export async function reverseGeocode(
lat: number,
lng: number
): Promise<MapboxResult | null> {
if (!MAPBOX_TOKEN || MAPBOX_TOKEN.startsWith("YOUR_")) {
console.warn("⚠️ Mapbox Token não configurado");
return null;
}
try {
const url =
`https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json?` +
`access_token=${MAPBOX_TOKEN}&` +
`language=pt&` +
`types=address,place`;
const response = await fetch(url);
const data = await response.json();
if (data.features && data.features.length > 0) {
const feature = data.features[0];
const context = feature.context || [];
const place = context.find((c: any) => c.id.startsWith("place"));
const region = context.find((c: any) => c.id.startsWith("region"));
const postcode = context.find((c: any) => c.id.startsWith("postcode"));
// Extrair número se houver
const addressMatch = feature.address || "";
return {
description: feature.place_name,
street: feature.text,
number: addressMatch.toString(),
city: place?.text || "",
state: region?.short_code?.replace("BR-", "") || region?.text || "",
zip: postcode?.text || "",
lat,
lng,
mapLink: `https://www.google.com/maps/search/?api=1&query=${lat},${lng}`,
};
}
return null;
} catch (error) {
console.error("Erro no reverse geocode:", error);
return null;
}
}