import React, { useState, useEffect, useRef } from "react"; import { X, MapPin, Save, Search } from "lucide-react"; import { CustomerAddress, customerService } from "../../src/services/customer.service"; interface AddressFormModalProps { isOpen: boolean; customerId: number | null; address?: CustomerAddress | null; onClose: () => void; onSave: (address: CustomerAddress) => void; } declare global { interface Window { google: any; initMap: () => void; } } const AddressFormModal: React.FC = ({ isOpen, customerId, address, onClose, onSave, }) => { const [formData, setFormData] = useState({ zipCode: "", address: "", number: "", complement: "", neighborhood: "", city: "", state: "", referencePoint: "", note: "", addressType: "Casa", isPrimary: false, }); const [errors, setErrors] = useState>({}); const [isLoading, setIsLoading] = useState(false); const [mapLoaded, setMapLoaded] = useState(false); const [map, setMap] = useState(null); const [marker, setMarker] = useState(null); const [geocoder, setGeocoder] = useState(null); const mapRef = useRef(null); const [coordinates, setCoordinates] = useState<{ lat: number; lng: number } | null>(null); useEffect(() => { if (isOpen) { if (address) { setFormData({ zipCode: address.zipCode || "", address: address.address || "", number: address.number || "", complement: address.complement || "", neighborhood: address.neighborhood || "", city: address.city || "", state: address.state || "", referencePoint: address.referencePoint || "", note: address.note || "", addressType: address.addressType || "Casa", isPrimary: address.isPrimary || false, }); if (address.latitude && address.longitude) { setCoordinates({ lat: address.latitude, lng: address.longitude }); } } else { setFormData({ zipCode: "", address: "", number: "", complement: "", neighborhood: "", city: "", state: "", referencePoint: "", note: "", addressType: "Casa", isPrimary: false, }); setCoordinates(null); } loadGoogleMaps(); } }, [isOpen, address]); const loadGoogleMaps = () => { if (window.google && window.google.maps) { initializeMap(); return; } if (!document.querySelector('script[src*="maps.googleapis.com"]')) { const script = document.createElement("script"); const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY || ""; if (apiKey) { script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=initMap`; script.async = true; script.defer = true; window.initMap = initializeMap; document.head.appendChild(script); } else { console.warn("Google Maps API Key não configurada. Configure VITE_GOOGLE_MAPS_API_KEY no arquivo .env"); } } else { initializeMap(); } }; const initializeMap = () => { if (!mapRef.current || !window.google) return; const initialCenter = coordinates || { lat: -23.5505, lng: -46.6333 }; // São Paulo default const mapInstance = new window.google.maps.Map(mapRef.current, { center: initialCenter, zoom: coordinates ? 15 : 10, mapTypeControl: false, streetViewControl: false, fullscreenControl: false, }); const geocoderInstance = new window.google.maps.Geocoder(); setGeocoder(geocoderInstance); setMap(mapInstance); let markerInstance: any = null; if (coordinates) { markerInstance = new window.google.maps.Marker({ position: coordinates, map: mapInstance, draggable: true, animation: window.google.maps.Animation.DROP, }); markerInstance.addListener("dragend", (e: any) => { const newPosition = { lat: e.latLng.lat(), lng: e.latLng.lng(), }; setCoordinates(newPosition); reverseGeocode(newPosition); }); } else { // Adiciona marcador no centro inicial markerInstance = new window.google.maps.Marker({ position: initialCenter, map: mapInstance, draggable: true, animation: window.google.maps.Animation.DROP, }); markerInstance.addListener("dragend", (e: any) => { const newPosition = { lat: e.latLng.lat(), lng: e.latLng.lng(), }; setCoordinates(newPosition); reverseGeocode(newPosition); }); } setMarker(markerInstance); // Click no mapa para adicionar/mover marcador mapInstance.addListener("click", (e: any) => { const newPosition = { lat: e.latLng.lat(), lng: e.latLng.lng(), }; setCoordinates(newPosition); if (markerInstance) { markerInstance.setPosition(newPosition); } else { markerInstance = new window.google.maps.Marker({ position: newPosition, map: mapInstance, draggable: true, }); markerInstance.addListener("dragend", (e: any) => { const newPos = { lat: e.latLng.lat(), lng: e.latLng.lng(), }; setCoordinates(newPos); reverseGeocode(newPos); }); setMarker(markerInstance); } reverseGeocode(newPosition); }); // Autocomplete para busca de endereço const autocomplete = new window.google.maps.places.Autocomplete( document.getElementById("address-search") as HTMLInputElement, { types: ["address"] } ); autocomplete.addListener("place_changed", () => { const place = autocomplete.getPlace(); if (place.geometry) { const location = place.geometry.location; const newPosition = { lat: location.lat(), lng: location.lng(), }; setCoordinates(newPosition); mapInstance.setCenter(newPosition); mapInstance.setZoom(15); if (markerInstance) { markerInstance.setPosition(newPosition); } // Preencher campos do formulário const addressComponents = place.address_components || []; addressComponents.forEach((component: any) => { const type = component.types[0]; if (type === "street_number") { setFormData((prev) => ({ ...prev, number: component.long_name })); } else if (type === "route") { setFormData((prev) => ({ ...prev, address: component.long_name })); } else if (type === "sublocality_level_1" || type === "neighborhood") { setFormData((prev) => ({ ...prev, neighborhood: component.long_name })); } else if (type === "locality") { setFormData((prev) => ({ ...prev, city: component.long_name })); } else if (type === "administrative_area_level_1") { setFormData((prev) => ({ ...prev, state: component.short_name })); } else if (type === "postal_code") { setFormData((prev) => ({ ...prev, zipCode: component.long_name })); } }); if (place.formatted_address) { setFormData((prev) => ({ ...prev, address: place.formatted_address.split(",")[0] })); } } }); setMapLoaded(true); }; const reverseGeocode = (position: { lat: number; lng: number }) => { if (!geocoder) return; geocoder.geocode({ location: position }, (results: any[], status: string) => { if (status === "OK" && results[0]) { const result = results[0]; const addressComponents = result.address_components || []; addressComponents.forEach((component: any) => { const type = component.types[0]; if (type === "street_number") { setFormData((prev) => ({ ...prev, number: component.long_name })); } else if (type === "route") { setFormData((prev) => ({ ...prev, address: component.long_name })); } else if (type === "sublocality_level_1" || type === "neighborhood") { setFormData((prev) => ({ ...prev, neighborhood: component.long_name })); } else if (type === "locality") { setFormData((prev) => ({ ...prev, city: component.long_name })); } else if (type === "administrative_area_level_1") { setFormData((prev) => ({ ...prev, state: component.short_name })); } else if (type === "postal_code") { setFormData((prev) => ({ ...prev, zipCode: component.long_name })); } }); } }); }; const handleSearchByCEP = async () => { if (!formData.zipCode || formData.zipCode.length < 8) return; const cep = formData.zipCode.replace(/\D/g, ""); if (cep.length !== 8) return; try { const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`); const data = await response.json(); if (!data.erro) { setFormData((prev) => ({ ...prev, address: data.logradouro || "", neighborhood: data.bairro || "", city: data.localidade || "", state: data.uf || "", })); // Geocodificar o endereço completo if (geocoder && data.logradouro) { const fullAddress = `${data.logradouro}, ${data.localidade}, ${data.uf}, Brasil`; geocoder.geocode({ address: fullAddress }, (results: any[], status: string) => { if (status === "OK" && results[0]) { const location = results[0].geometry.location; const newPosition = { lat: location.lat(), lng: location.lng(), }; setCoordinates(newPosition); if (map) { map.setCenter(newPosition); map.setZoom(15); } if (marker) { marker.setPosition(newPosition); } else if (map) { const newMarker = new window.google.maps.Marker({ position: newPosition, map: map, draggable: true, }); newMarker.addListener("dragend", (e: any) => { const newPos = { lat: e.latLng.lat(), lng: e.latLng.lng(), }; setCoordinates(newPos); reverseGeocode(newPos); }); setMarker(newMarker); } } }); } } } catch (error) { console.error("Erro ao buscar CEP:", error); } }; const handleChange = (field: string, value: string | boolean) => { setFormData((prev) => ({ ...prev, [field]: value })); if (errors[field]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field]; return newErrors; }); } }; const validate = () => { const newErrors: Record = {}; if (!formData.zipCode) newErrors.zipCode = "CEP é obrigatório"; if (!formData.address) newErrors.address = "Endereço é obrigatório"; if (!formData.number) newErrors.number = "Número é obrigatório"; if (!formData.city) newErrors.city = "Cidade é obrigatória"; if (!formData.state) newErrors.state = "Estado é obrigatório"; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSave = async () => { if (!validate() || !customerId) return; setIsLoading(true); try { const addressData: Partial = { ...formData, latitude: coordinates?.lat, longitude: coordinates?.lng, }; let savedAddress: CustomerAddress | null; if (address?.id) { // Atualizar endereço existente // TODO: Implementar updateAddress no service savedAddress = await customerService.createAddress(customerId, addressData); } else { // Criar novo endereço savedAddress = await customerService.createAddress(customerId, addressData); } if (savedAddress) { onSave(savedAddress); onClose(); } } catch (error: any) { console.error("Erro ao salvar endereço:", error); setErrors({ general: error.message || "Erro ao salvar endereço" }); } finally { setIsLoading(false); } }; if (!isOpen) return null; return (
{/* Header */}

{address ? "Editar Endereço" : "Cadastrar Novo Endereço"}

{address ? "Atualize os dados do endereço" : "Preencha os dados e selecione no mapa"}

{/* Content */}
{/* Formulário */}

Dados do Endereço

{/* Busca por CEP ou Endereço */}
handleChange("zipCode", e.target.value)} className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${ errors.zipCode ? "border-red-500" : "border-slate-200" } focus:outline-none focus:ring-2 focus:ring-orange-500/20`} placeholder="00000-000" /> {errors.zipCode && (

{errors.zipCode}

)}
handleChange("address", e.target.value)} className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${ errors.address ? "border-red-500" : "border-slate-200" } focus:outline-none focus:ring-2 focus:ring-orange-500/20`} /> {errors.address && (

{errors.address}

)}
handleChange("number", e.target.value)} className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${ errors.number ? "border-red-500" : "border-slate-200" } focus:outline-none focus:ring-2 focus:ring-orange-500/20`} /> {errors.number && (

{errors.number}

)}
handleChange("complement", e.target.value)} className="w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border border-slate-200 focus:outline-none focus:ring-2 focus:ring-orange-500/20" />
handleChange("neighborhood", e.target.value)} className="w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border border-slate-200 focus:outline-none focus:ring-2 focus:ring-orange-500/20" />
handleChange("city", e.target.value)} className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${ errors.city ? "border-red-500" : "border-slate-200" } focus:outline-none focus:ring-2 focus:ring-orange-500/20`} /> {errors.city && (

{errors.city}

)}
handleChange("state", e.target.value)} className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${ errors.state ? "border-red-500" : "border-slate-200" } focus:outline-none focus:ring-2 focus:ring-orange-500/20`} maxLength={2} placeholder="SP" /> {errors.state && (

{errors.state}

)}
handleChange("referencePoint", e.target.value)} className="w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border border-slate-200 focus:outline-none focus:ring-2 focus:ring-orange-500/20" />