diff --git a/app-9xzmfic2e4g1/src/components/trip/Map.tsx b/app-9xzmfic2e4g1/src/components/trip/Map.tsx index fcc0315..042857e 100644 --- a/app-9xzmfic2e4g1/src/components/trip/Map.tsx +++ b/app-9xzmfic2e4g1/src/components/trip/Map.tsx @@ -1,9 +1,8 @@ -import { useEffect, useRef, useState, useMemo } from 'react'; -import { ItineraryDay } from '@/db/api'; +import { useEffect, useRef, useState, useMemo, useCallback } from 'react'; +import { ItineraryDay, Place } from '@/db/api'; import { initGoogleMaps } from '@/lib/google-maps-loader'; import { Button } from '@/components/ui/button'; -import { ZoomIn, ZoomOut, Maximize2, Navigation2, Map as MapIcon, Layers } from 'lucide-react'; -import { Badge } from '@/components/ui/badge'; +import { ZoomIn, ZoomOut, Maximize2, Plus, Loader2 } from 'lucide-react'; import api from '@/db/api'; import { cn } from '@/lib/utils'; @@ -11,31 +10,33 @@ interface MapProps { itinerary: { days: ItineraryDay[] }; activePlaceId: string | null; onMarkerClick: (id: string) => void; + onAddPlace?: (place: Place) => void; // yeni: haritadan ekleme } -// More standard and vibrant color palette for Wanderlog feel const DAY_COLORS = [ - '#EA580C', // Orange-600 - '#2563EB', // Blue-600 - '#059669', // Emerald-600 - '#7C3AED', // Violet-600 - '#DB2777', // Pink-600 + '#EA580C', + '#2563EB', + '#059669', + '#7C3AED', + '#DB2777', ]; -export function TripMap({ itinerary, activePlaceId, onMarkerClick }: MapProps) { +export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }: MapProps) { const mapRef = useRef(null); const [googleMap, setGoogleMap] = useState(null); const [error, setError] = useState(null); - const [mapType, setMapType] = useState<'roadmap' | 'satellite' | 'terrain'>('roadmap'); const markersRef = useRef<{ [key: string]: { marker: google.maps.Marker; dayIndex: number } }>({}); const polylinesRef = useRef([]); const infoWindowRef = useRef(null); + const placesServiceRef = useRef(null); + const [addingPlaceId, setAddingPlaceId] = useState(null); - const itineraryKey = useMemo(() => + const itineraryKey = useMemo(() => JSON.stringify(itinerary.days.map(d => d.items.map(i => i.place_id))), [itinerary] ); + // ── Map init ────────────────────────────────────────────────────────────── useEffect(() => { const loadMap = async () => { try { @@ -44,47 +45,135 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick }: MapProps) { setError('Google Maps API anahtarı eksik.'); return; } - await initGoogleMaps(apiKey); - + if (mapRef.current && !googleMap) { const map = new google.maps.Map(mapRef.current, { center: { lat: 38.6431, lng: 34.8347 }, zoom: 12, styles: [ - // Standard clean map style - { - "featureType": "poi.business", - "stylers": [{ "visibility": "off" }] - }, - { - "featureType": "poi.park", - "elementType": "labels.text", - "stylers": [{ "visibility": "on" }] - } + { featureType: 'poi.business', stylers: [{ visibility: 'off' }] }, + { featureType: 'poi.park', elementType: 'labels.text', stylers: [{ visibility: 'on' }] }, ], mapTypeControl: false, streetViewControl: false, fullscreenControl: false, zoomControl: false, - clickableIcons: true, + clickableIcons: true, // POI'lere tıklanabilir }); - setGoogleMap(map); + infoWindowRef.current = new google.maps.InfoWindow(); + placesServiceRef.current = new google.maps.places.PlacesService(map); + + // ── POI tıklama ─────────────────────────────────────────────────── + if (onAddPlace) { + map.addListener('click', (e: google.maps.MapMouseEvent & { placeId?: string }) => { + if (!e.placeId) return; // sadece POI tıklamalarını yakala + e.stop?.(); // varsayılan Google bilgi penceresini engelle + + const placeId = e.placeId; + infoWindowRef.current?.close(); + + // Önce "yükleniyor" info window göster + const loadingContent = ` +
+
+ Yer bilgisi yükleniyor... +
+ + `; + infoWindowRef.current?.setContent(loadingContent); + infoWindowRef.current?.setPosition(e.latLng!); + infoWindowRef.current?.open(map); + + // Places Details çek + placesServiceRef.current?.getDetails( + { placeId, fields: ['place_id', 'name', 'formatted_address', 'geometry', 'rating', 'photos', 'types'] }, + (place, status) => { + if (status !== google.maps.places.PlacesServiceStatus.OK || !place || !place.geometry?.location) { + infoWindowRef.current?.close(); + return; + } + + const photoUrl = place.photos?.[0]?.getUrl({ maxWidth: 400 }) || ''; + const btnId = `map-add-btn-${placeId}`; + + const content = ` +
+ ${photoUrl ? ` +
+ +
+ ` : ''} +
+

${place.name}

+

${place.formatted_address || ''}

+
+ ${place.rating ? `★ ${place.rating}` : ''} + +
+
+
+ `; + + infoWindowRef.current?.setContent(content); + + // Buton tıklamasını DOM'dan yakala + google.maps.event.addListenerOnce(infoWindowRef.current!, 'domready', () => { + const btn = document.getElementById(btnId); + if (!btn) return; + + btn.addEventListener('click', () => { + const newPlace: Place = { + place_id: place.place_id || `map-${Date.now()}`, + name: place.name || '', + lat: place.geometry!.location!.lat(), + lng: place.geometry!.location!.lng(), + rating: place.rating, + formatted_address: place.formatted_address || '', + photo_reference: photoUrl, + description: place.types?.join(', ') || 'Turistik Nokta', + category: (place.types?.[0] || 'point_of_interest').replace(/_/g, ' '), + estimated_duration_minutes: 60, + start_time: '09:00', + end_time: '10:00', + }; + + // Buton feedback + btn.textContent = '✓ Eklendi!'; + btn.style.background = '#059669'; + btn.style.pointerEvents = 'none'; + + onAddPlace(newPlace); + + setTimeout(() => infoWindowRef.current?.close(), 1200); + }); + }); + } + ); + }); + } + + setGoogleMap(map); } - } catch (error) { + } catch { setError('Google Maps yüklenemedi.'); } }; - if (!googleMap) loadMap(); - }, [googleMap]); + if (!googleMap) loadMap(); + }, [googleMap, onAddPlace]); + + // ── Markers & polylines ─────────────────────────────────────────────────── useEffect(() => { if (!googleMap || !itinerary?.days) return; Object.values(markersRef.current).forEach(({ marker }) => marker.setMap(null)); markersRef.current = {}; - polylinesRef.current.forEach(polyline => polyline.setMap(null)); + polylinesRef.current.forEach(p => p.setMap(null)); polylinesRef.current = []; const bounds = new google.maps.LatLngBounds(); @@ -111,7 +200,7 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick }: MapProps) { fontWeight: '900', }, icon: { - path: "M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z", + path: 'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z', fillColor: dayColor, fillOpacity: 1, strokeWeight: 2, @@ -125,24 +214,23 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick }: MapProps) { marker.addListener('click', () => { onMarkerClick(item.place_id); - if (infoWindowRef.current) { - const photoUrl = item.photo_reference ? ( - item.photo_reference.startsWith('http') ? item.photo_reference : api.getPhotoUrl(item.photo_reference) - ) : ''; - - infoWindowRef.current.setContent(` -
- ${photoUrl ? `` : ''} -

${item.name}

-

${item.category}

-
- ★ ${item.rating || 'N/A'} - Yol Tarifi -
+ const photoUrl = item.photo_reference + ? (item.photo_reference.startsWith('http') ? item.photo_reference : api.getPhotoUrl(item.photo_reference)) + : ''; + + infoWindowRef.current?.setContent(` +
+ ${photoUrl ? `` : ''} +

${item.name}

+

${item.category}

+
+ ★ ${item.rating || 'N/A'} + Yol Tarifi
- `); - infoWindowRef.current.open(googleMap, marker); - } +
+ `); + infoWindowRef.current?.open(googleMap, marker); }); markersRef.current[item.place_id] = { marker, dayIndex }; @@ -167,30 +255,30 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick }: MapProps) { } }, [googleMap, itineraryKey]); + // ── Active marker highlight ─────────────────────────────────────────────── useEffect(() => { - if (googleMap && activePlaceId && markersRef.current[activePlaceId]) { - const { marker, dayIndex } = markersRef.current[activePlaceId]; - googleMap.panTo(marker.getPosition()!); - - Object.keys(markersRef.current).forEach(id => { - const { marker: m, dayIndex: dIdx } = markersRef.current[id]; - const color = DAY_COLORS[dIdx % DAY_COLORS.length]; - const isActive = id === activePlaceId; - - m.setIcon({ - path: "M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z", - fillColor: color, - fillOpacity: 1, - strokeWeight: isActive ? 3 : 2, - strokeColor: isActive ? '#000000' : '#ffffff', - scale: isActive ? 1.8 : 1.4, - anchor: new google.maps.Point(12, 22), - labelOrigin: new google.maps.Point(12, 9), - }); - m.setZIndex(isActive ? 1000 : 1); - m.setAnimation(isActive ? google.maps.Animation.BOUNCE : null); + if (!googleMap || !activePlaceId || !markersRef.current[activePlaceId]) return; + + const { marker } = markersRef.current[activePlaceId]; + googleMap.panTo(marker.getPosition()!); + + Object.keys(markersRef.current).forEach(id => { + const { marker: m, dayIndex: dIdx } = markersRef.current[id]; + const color = DAY_COLORS[dIdx % DAY_COLORS.length]; + const isActive = id === activePlaceId; + m.setIcon({ + path: 'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z', + fillColor: color, + fillOpacity: 1, + strokeWeight: isActive ? 3 : 2, + strokeColor: isActive ? '#000000' : '#ffffff', + scale: isActive ? 1.8 : 1.4, + anchor: new google.maps.Point(12, 22), + labelOrigin: new google.maps.Point(12, 9), }); - } + m.setZIndex(isActive ? 1000 : 1); + m.setAnimation(isActive ? google.maps.Animation.BOUNCE : null); + }); }, [activePlaceId, googleMap]); return ( @@ -202,25 +290,37 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick }: MapProps) { ) : ( <>
- - {/* Compact Map Controls */} + + {/* Haritadan ekleme ipucu */} + {onAddPlace && ( +
+
+ + Haritada bir yere tıklayarak plana ekleyin +
+
+ )} + + {/* Zoom controls */}
- -
- -