693 lines
26 KiB
TypeScript
693 lines
26 KiB
TypeScript
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<AddressFormModalProps> = ({
|
|
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<Record<string, string>>({});
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [mapLoaded, setMapLoaded] = useState(false);
|
|
const [map, setMap] = useState<any>(null);
|
|
const [marker, setMarker] = useState<any>(null);
|
|
const [geocoder, setGeocoder] = useState<any>(null);
|
|
const mapRef = useRef<HTMLDivElement>(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<string, string> = {};
|
|
|
|
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<CustomerAddress> = {
|
|
...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 (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
|
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-6xl max-h-[90vh] flex flex-col overflow-hidden">
|
|
{/* Header */}
|
|
<div className="p-6 bg-[#002147] text-white rounded-t-3xl relative overflow-hidden flex-shrink-0">
|
|
<div className="relative z-10 flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-12 h-12 bg-orange-500/20 rounded-2xl flex items-center justify-center">
|
|
<MapPin className="w-6 h-6 text-orange-400" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-xl font-black">
|
|
{address ? "Editar Endereço" : "Cadastrar Novo Endereço"}
|
|
</h3>
|
|
<p className="text-xs text-orange-400 font-bold uppercase tracking-wider mt-0.5">
|
|
{address ? "Atualize os dados do endereço" : "Preencha os dados e selecione no mapa"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="w-10 h-10 flex items-center justify-center rounded-xl hover:bg-white/10 transition-colors"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<div className="absolute right-[-10%] top-[-10%] w-32 h-32 bg-orange-400/10 rounded-full blur-2xl"></div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 overflow-auto p-6">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Formulário */}
|
|
<div className="space-y-4">
|
|
<h4 className="text-sm font-black uppercase text-slate-600 mb-4">
|
|
Dados do Endereço
|
|
</h4>
|
|
|
|
{/* Busca por CEP ou Endereço */}
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Buscar por CEP ou Endereço
|
|
</label>
|
|
<div className="flex gap-2">
|
|
<input
|
|
id="address-search"
|
|
type="text"
|
|
placeholder="Digite o CEP ou endereço..."
|
|
className="flex-1 px-4 py-3 bg-white border-2 border-slate-200 rounded-xl font-bold text-slate-700 outline-none focus:border-orange-500 transition-all"
|
|
/>
|
|
<button
|
|
onClick={handleSearchByCEP}
|
|
className="px-4 py-3 bg-[#002147] text-white rounded-xl font-bold hover:bg-[#001a36] transition-colors"
|
|
>
|
|
<Search className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
CEP *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.zipCode}
|
|
onChange={(e) => 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 && (
|
|
<p className="text-red-500 text-xs mt-1">{errors.zipCode}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Tipo
|
|
</label>
|
|
<select
|
|
value={formData.addressType}
|
|
onChange={(e) => handleChange("addressType", 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"
|
|
>
|
|
<option value="Casa">Casa</option>
|
|
<option value="Trabalho">Trabalho</option>
|
|
<option value="Outro">Outro</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Endereço *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.address}
|
|
onChange={(e) => 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 && (
|
|
<p className="text-red-500 text-xs mt-1">{errors.address}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Número *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.number}
|
|
onChange={(e) => 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 && (
|
|
<p className="text-red-500 text-xs mt-1">{errors.number}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Complemento
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.complement}
|
|
onChange={(e) => 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"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Bairro
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.neighborhood}
|
|
onChange={(e) => 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"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Cidade *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.city}
|
|
onChange={(e) => 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 && (
|
|
<p className="text-red-500 text-xs mt-1">{errors.city}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Estado *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.state}
|
|
onChange={(e) => 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 && (
|
|
<p className="text-red-500 text-xs mt-1">{errors.state}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Ponto de Referência
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={formData.referencePoint}
|
|
onChange={(e) => 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"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-black uppercase text-slate-400 mb-2">
|
|
Observações
|
|
</label>
|
|
<textarea
|
|
value={formData.note}
|
|
onChange={(e) => handleChange("note", e.target.value)}
|
|
rows={3}
|
|
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 resize-none"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
id="isPrimary"
|
|
checked={formData.isPrimary}
|
|
onChange={(e) => handleChange("isPrimary", e.target.checked)}
|
|
className="w-5 h-5 rounded border-slate-300 text-[#002147] focus:ring-2 focus:ring-orange-500/20"
|
|
/>
|
|
<label htmlFor="isPrimary" className="text-sm font-bold text-slate-700">
|
|
Definir como endereço principal
|
|
</label>
|
|
</div>
|
|
|
|
{errors.general && (
|
|
<div className="p-3 bg-red-50 border border-red-200 rounded-xl">
|
|
<p className="text-red-600 text-sm font-bold">{errors.general}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Mapa */}
|
|
<div>
|
|
<h4 className="text-sm font-black uppercase text-slate-600 mb-4">
|
|
Localização no Mapa
|
|
</h4>
|
|
<div className="relative">
|
|
<div
|
|
ref={mapRef}
|
|
className="w-full h-[500px] rounded-2xl border-2 border-slate-200 overflow-hidden"
|
|
/>
|
|
{!mapLoaded && (
|
|
<div className="absolute inset-0 flex items-center justify-center bg-slate-100">
|
|
<div className="text-center">
|
|
<div className="w-12 h-12 border-4 border-[#002147] border-t-transparent rounded-full animate-spin mx-auto mb-2"></div>
|
|
<p className="text-sm font-bold text-slate-600">Carregando mapa...</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="mt-3 p-3 bg-slate-50 rounded-xl">
|
|
<p className="text-xs text-slate-600">
|
|
<strong>Dica:</strong> Clique no mapa ou arraste o marcador para definir a localização exata do endereço.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="p-6 border-t border-slate-200 flex-shrink-0 flex items-center justify-end gap-3">
|
|
<button
|
|
onClick={onClose}
|
|
className="px-6 py-3 rounded-xl font-bold text-slate-700 hover:bg-slate-100 transition-colors"
|
|
>
|
|
Cancelar
|
|
</button>
|
|
<button
|
|
onClick={handleSave}
|
|
disabled={isLoading}
|
|
className="flex items-center gap-2 px-6 py-3 rounded-xl font-black bg-[#002147] text-white hover:bg-[#001a36] transition-all shadow-lg shadow-[#002147]/20 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
Salvando...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Save className="w-4 h-4" />
|
|
Salvar Endereço
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AddressFormModal;
|
|
|