Edit app-9xzmfic2e4g1/src/components/trip/Map.tsx via Editor

This commit is contained in:
Flatlogic Bot 2026-03-04 11:34:16 +00:00
parent abc2e658a0
commit 773d823e75

View File

@ -2,15 +2,16 @@ 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, Plus, Loader2 } from 'lucide-react';
import { ZoomIn, ZoomOut, Maximize2, Plus, Star, Clock, ChevronRight, Lightbulb, CheckCircle2, Loader2, X, MessageSquare } from 'lucide-react';
import api from '@/db/api';
import { cn } from '@/lib/utils';
import { motion, AnimatePresence } from 'framer-motion';
interface MapProps {
itinerary: { days: ItineraryDay[] };
activePlaceId: string | null;
onMarkerClick: (id: string) => void;
onAddPlace?: (place: Place) => void; // yeni: haritadan ekleme
onAddPlace?: (place: Place) => void;
}
const DAY_COLORS = [
@ -21,21 +22,102 @@ const DAY_COLORS = [
'#DB2777',
];
interface PlaceDetail {
place_id: string;
name: string;
summary?: string;
rating?: number;
total_ratings?: number;
is_open_now?: boolean | null;
opening_hours?: string[] | null;
why_visit: string[];
tips: string[];
reviews: { author: string; rating: number; text: string; time: string }[];
}
interface SelectedPOI {
place_id: string;
name: string;
lat: number;
lng: number;
photoUrl: string;
category: string;
formatted_address?: string;
rating?: number;
}
export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }: MapProps) {
const mapRef = useRef<HTMLDivElement>(null);
const [googleMap, setGoogleMap] = useState<google.maps.Map | null>(null);
const [error, setError] = useState<string | null>(null);
const markersRef = useRef<{ [key: string]: { marker: google.maps.Marker; dayIndex: number } }>({});
const polylinesRef = useRef<google.maps.Polyline[]>([]);
const infoWindowRef = useRef<google.maps.InfoWindow | null>(null);
const placesServiceRef = useRef<google.maps.places.PlacesService | null>(null);
const [addingPlaceId, setAddingPlaceId] = useState<string | null>(null);
// Detail panel state
const [selectedPOI, setSelectedPOI] = useState<SelectedPOI | null>(null);
const [placeDetail, setPlaceDetail] = useState<PlaceDetail | null>(null);
const [detailLoading, setDetailLoading] = useState(false);
const [added, setAdded] = useState(false);
const [activeTab, setActiveTab] = useState<'about' | 'reviews'>('about');
const itineraryKey = useMemo(() =>
JSON.stringify(itinerary.days.map(d => d.items.map(i => i.place_id))),
[itinerary]
);
// ── Reset panel on itinerary change ──────────────────────────────────────
useEffect(() => {
setSelectedPOI(null);
setPlaceDetail(null);
setAdded(false);
}, [itineraryKey]);
// ── Fetch rich details ────────────────────────────────────────────────────
const fetchPlaceDetail = useCallback(async (poi: SelectedPOI) => {
setDetailLoading(true);
setPlaceDetail(null);
setActiveTab('about');
try {
const data = await api.getPlaceDetails({
place_id: poi.place_id,
name: poi.name,
category: poi.category,
});
setPlaceDetail(data);
} catch (e) {
console.error('Place detail fetch error:', e);
} finally {
setDetailLoading(false);
}
}, []);
// ── Handle add ────────────────────────────────────────────────────────────
const handleAdd = useCallback(() => {
if (!selectedPOI || !onAddPlace) return;
const place: Place = {
place_id: selectedPOI.place_id,
name: selectedPOI.name,
lat: selectedPOI.lat,
lng: selectedPOI.lng,
rating: selectedPOI.rating,
formatted_address: selectedPOI.formatted_address || '',
photo_reference: selectedPOI.photoUrl,
description: selectedPOI.category,
category: selectedPOI.category,
estimated_duration_minutes: 60,
start_time: '09:00',
end_time: '10:00',
};
onAddPlace(place);
setAdded(true);
setTimeout(() => {
setSelectedPOI(null);
setPlaceDetail(null);
setAdded(false);
}, 1500);
}, [selectedPOI, onAddPlace]);
// ── Map init ──────────────────────────────────────────────────────────────
useEffect(() => {
const loadMap = async () => {
@ -59,99 +141,45 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
streetViewControl: false,
fullscreenControl: false,
zoomControl: false,
clickableIcons: true, // POI'lere tıklanabilir
clickableIcons: true,
});
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
if (!e.placeId) return;
e.stop?.();
const placeId = e.placeId;
infoWindowRef.current?.close();
setAdded(false);
// Önce "yükleniyor" info window göster
const loadingContent = `
<div style="padding:12px;min-width:180px;font-family:sans-serif;display:flex;align-items:center;gap:8px;">
<div style="width:16px;height:16px;border:2px solid #EA580C;border-top-color:transparent;border-radius:50%;animation:spin 0.7s linear infinite;"></div>
<span style="font-size:13px;color:#6B7280;">Yer bilgisi yükleniyor...</span>
</div>
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>
`;
infoWindowRef.current?.setContent(loadingContent);
infoWindowRef.current?.setPosition(e.latLng!);
infoWindowRef.current?.open(map);
// Places Details çek
// Önce temel bilgiyi Google'dan çek, sonra panel aç
placesServiceRef.current?.getDetails(
{ placeId, fields: ['place_id', 'name', 'formatted_address', 'geometry', 'rating', 'photos', 'types'] },
{
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;
}
if (status !== google.maps.places.PlacesServiceStatus.OK || !place?.geometry?.location) return;
const photoUrl = place.photos?.[0]?.getUrl({ maxWidth: 400 }) || '';
const btnId = `map-add-btn-${placeId}`;
const photoUrl = place.photos?.[0]?.getUrl({ maxWidth: 600 }) || '';
const category = (place.types?.[0] || 'point_of_interest').replace(/_/g, ' ');
const content = `
<div style="font-family:sans-serif;min-width:220px;max-width:260px;overflow:hidden;border-radius:8px;">
${photoUrl ? `
<div style="width:100%;height:110px;overflow:hidden;margin-bottom:0;">
<img src="${photoUrl}" style="width:100%;height:100%;object-fit:cover;" />
</div>
` : ''}
<div style="padding:10px 12px 12px;">
<p style="margin:0 0 2px;font-size:14px;font-weight:800;color:#111827;line-height:1.3;">${place.name}</p>
<p style="margin:0 0 8px;font-size:11px;color:#9CA3AF;">${place.formatted_address || ''}</p>
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
${place.rating ? `<span style="font-size:11px;font-weight:700;color:#F59E0B;">★ ${place.rating}</span>` : '<span></span>'}
<button id="${btnId}"
style="display:flex;align-items:center;gap:4px;background:#EA580C;color:white;border:none;border-radius:8px;padding:6px 12px;font-size:11px;font-weight:800;cursor:pointer;letter-spacing:0.05em;text-transform:uppercase;">
+ Plana Ekle
</button>
</div>
</div>
</div>
`;
const poi: SelectedPOI = {
place_id: place.place_id || placeId,
name: place.name || '',
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng(),
photoUrl,
category,
formatted_address: place.formatted_address || '',
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);
});
});
setSelectedPOI(poi);
fetchPlaceDetail(poi);
}
);
});
@ -165,7 +193,7 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
};
if (!googleMap) loadMap();
}, [googleMap, onAddPlace]);
}, [googleMap, onAddPlace, fetchPlaceDetail]);
// ── Markers & polylines ───────────────────────────────────────────────────
useEffect(() => {
@ -177,6 +205,7 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
polylinesRef.current = [];
const bounds = new google.maps.LatLngBounds();
const infoWindow = new google.maps.InfoWindow();
itinerary.days.forEach((day, dayIndex) => {
const dayColor = DAY_COLORS[dayIndex % DAY_COLORS.length];
@ -185,7 +214,6 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
day.items.forEach((item, itemIndex) => {
const position = { lat: item.lat, lng: item.lng };
dayPath.push(position);
const isActive = item.place_id === activePlaceId;
const marker = new google.maps.Marker({
@ -193,12 +221,7 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
map: googleMap,
title: item.name,
zIndex: isActive ? 1000 : 1,
label: {
text: (itemIndex + 1).toString(),
color: 'white',
fontSize: '11px',
fontWeight: '900',
},
label: { text: (itemIndex + 1).toString(), color: 'white', fontSize: '11px', 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',
fillColor: dayColor,
@ -217,20 +240,19 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
const photoUrl = item.photo_reference
? (item.photo_reference.startsWith('http') ? item.photo_reference : api.getPhotoUrl(item.photo_reference))
: '';
infoWindowRef.current?.setContent(`
infoWindow.setContent(`
<div style="padding:8px;min-width:200px;font-family:sans-serif;">
${photoUrl ? `<img src="${photoUrl}" style="width:100%;height:100px;object-fit:cover;border-radius:8px;margin-bottom:8px;" />` : ''}
<h4 style="margin:0 0 2px;font-size:14px;font-weight:bold;">${item.name}</h4>
<p style="margin:0 0 8px;font-size:11px;color:#6B7280;">${item.category}</p>
${photoUrl ? `<img src="${photoUrl}" style="width:100%;height:90px;object-fit:cover;border-radius:8px;margin-bottom:8px;" />` : ''}
<h4 style="margin:0 0 2px;font-size:13px;font-weight:bold;">${item.name}</h4>
<p style="margin:0 0 6px;font-size:10px;color:#6B7280;">${item.category}</p>
<div style="display:flex;align-items:center;justify-content:space-between;">
<span style="font-size:11px;font-weight:bold;"> ${item.rating || 'N/A'}</span>
<span style="font-size:11px;font-weight:bold;color:#F59E0B;"> ${item.rating || 'N/A'}</span>
<a href="https://www.google.com/maps/dir/?api=1&destination=${item.lat},${item.lng}" target="_blank"
style="color:#EA580C;font-size:11px;font-weight:bold;text-decoration:none;">Yol Tarifi</a>
style="color:#EA580C;font-size:11px;font-weight:bold;text-decoration:none;">Yol Tarifi </a>
</div>
</div>
`);
infoWindowRef.current?.open(googleMap, marker);
infoWindow.open(googleMap, marker);
});
markersRef.current[item.place_id] = { marker, dayIndex };
@ -239,11 +261,8 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
if (dayPath.length > 1) {
const polyline = new google.maps.Polyline({
path: dayPath,
geodesic: true,
strokeColor: dayColor,
strokeOpacity: 0.8,
strokeWeight: 3,
path: dayPath, geodesic: true,
strokeColor: dayColor, strokeOpacity: 0.8, strokeWeight: 3,
});
polyline.setMap(googleMap);
polylinesRef.current.push(polyline);
@ -258,18 +277,15 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
// ── Active marker highlight ───────────────────────────────────────────────
useEffect(() => {
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,
fillColor: color, fillOpacity: 1,
strokeWeight: isActive ? 3 : 2,
strokeColor: isActive ? '#000000' : '#ffffff',
scale: isActive ? 1.8 : 1.4,
@ -281,8 +297,9 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
});
}, [activePlaceId, googleMap]);
// ── Render ────────────────────────────────────────────────────────────────
return (
<div className="relative w-full h-full bg-gray-50">
<div className="relative w-full h-full bg-gray-50 overflow-hidden">
{error ? (
<div className="absolute inset-0 flex items-center justify-center">
<p className="text-sm font-medium text-gray-500">{error}</p>
@ -291,18 +308,18 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
<>
<div ref={mapRef} className="absolute inset-0 w-full h-full" />
{/* Haritadan ekleme ipucu */}
{onAddPlace && (
{/* İpucu */}
{onAddPlace && !selectedPOI && (
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 pointer-events-none">
<div className="bg-white/90 backdrop-blur-md px-4 py-2 rounded-full shadow-lg border border-white/60 flex items-center gap-2">
<Plus className="h-3.5 w-3.5 text-orange-500 shrink-0" />
<span className="text-[11px] font-bold text-gray-600">Haritada bir yere tıklayarak plana ekleyin</span>
<span className="text-[11px] font-bold text-gray-600">Haritada bir yere tıklayarak detayları görün</span>
</div>
</div>
)}
{/* Zoom controls */}
<div className="absolute top-4 right-4 flex flex-col gap-2">
{/* Zoom */}
<div className="absolute top-4 right-4 flex flex-col gap-2 z-10">
<div className="flex flex-col bg-white border rounded-lg shadow-sm overflow-hidden">
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-none border-b"
onClick={() => googleMap?.setZoom((googleMap.getZoom() || 12) + 1)}>
@ -324,6 +341,259 @@ export function TripMap({ itinerary, activePlaceId, onMarkerClick, onAddPlace }:
<Maximize2 className="h-4 w-4" />
</Button>
</div>
{/* ── Detay Paneli ───────────────────────────────────────────────── */}
<AnimatePresence>
{selectedPOI && (
<motion.div
initial={{ x: '100%', opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: '100%', opacity: 0 }}
transition={{ type: 'spring', damping: 28, stiffness: 300 }}
className="absolute top-0 right-0 bottom-0 w-[340px] bg-white shadow-2xl z-20 flex flex-col overflow-hidden"
>
{/* Fotoğraf header */}
<div className="relative h-44 shrink-0 bg-gray-100">
{selectedPOI.photoUrl ? (
<img
src={selectedPOI.photoUrl}
alt={selectedPOI.name}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-gradient-to-br from-orange-100 to-amber-50 flex items-center justify-center">
<span className="text-4xl">🗺</span>
</div>
)}
{/* Gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
{/* Kapat */}
<button
onClick={() => { setSelectedPOI(null); setPlaceDetail(null); setAdded(false); }}
className="absolute top-3 right-3 w-8 h-8 rounded-full bg-black/40 backdrop-blur-sm flex items-center justify-center text-white hover:bg-black/60 transition-all"
>
<X className="h-4 w-4" />
</button>
{/* Açık/Kapalı badge */}
{placeDetail?.is_open_now != null && (
<div className={cn(
'absolute top-3 left-3 px-2.5 py-1 rounded-full text-[10px] font-black backdrop-blur-sm',
placeDetail.is_open_now
? 'bg-green-500/90 text-white'
: 'bg-red-500/90 text-white'
)}>
{placeDetail.is_open_now ? '● Açık' : '● Kapalı'}
</div>
)}
{/* İsim */}
<div className="absolute bottom-3 left-3 right-3">
<p className="text-[10px] font-bold text-white/70 uppercase tracking-widest mb-0.5">
{selectedPOI.category}
</p>
<h3 className="text-lg font-black text-white leading-tight line-clamp-2">
{selectedPOI.name}
</h3>
</div>
</div>
{/* Rating + tabs */}
<div className="px-4 pt-3 pb-0 border-b shrink-0">
<div className="flex items-center justify-between mb-3">
{(selectedPOI.rating || placeDetail?.rating) && (
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-0.5">
{[1,2,3,4,5].map(i => (
<Star key={i}
className={cn('h-3.5 w-3.5', i <= Math.round(selectedPOI.rating || placeDetail?.rating || 0)
? 'fill-amber-400 text-amber-400'
: 'text-gray-200 fill-gray-200'
)}
/>
))}
</div>
<span className="text-sm font-black text-gray-800">
{(selectedPOI.rating || placeDetail?.rating)?.toFixed(1)}
</span>
{placeDetail?.total_ratings && (
<span className="text-xs text-gray-400">({placeDetail.total_ratings.toLocaleString()})</span>
)}
</div>
)}
</div>
{/* Tabs */}
<div className="flex gap-1">
{(['about', 'reviews'] as const).map(tab => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={cn(
'px-3 py-1.5 text-[11px] font-black uppercase tracking-wider rounded-t-lg transition-all border-b-2',
activeTab === tab
? 'text-orange-600 border-orange-600'
: 'text-gray-400 border-transparent hover:text-gray-600'
)}
>
{tab === 'about' ? 'Hakkında' : 'Yorumlar'}
</button>
))}
</div>
</div>
{/* İçerik */}
<div className="flex-1 overflow-y-auto">
{detailLoading ? (
<div className="flex flex-col items-center justify-center h-48 gap-3">
<Loader2 className="h-8 w-8 text-orange-500 animate-spin" />
<p className="text-xs font-bold text-gray-400">Detaylar yükleniyor...</p>
</div>
) : placeDetail ? (
<AnimatePresence mode="wait">
{activeTab === 'about' ? (
<motion.div
key="about"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
className="p-4 space-y-5"
>
{/* Özet */}
{placeDetail.summary && (
<p className="text-sm text-gray-600 leading-relaxed">{placeDetail.summary}</p>
)}
{/* Neden gitmelisiniz */}
{placeDetail.why_visit?.length > 0 && (
<div className="space-y-2">
<h4 className="text-[11px] font-black text-gray-900 uppercase tracking-widest flex items-center gap-1.5">
<ChevronRight className="h-3.5 w-3.5 text-orange-500" />
Neden gitmelisiniz?
</h4>
<div className="space-y-2">
{placeDetail.why_visit.map((reason, i) => (
<div key={i} className="flex items-start gap-2.5">
<div className="w-5 h-5 rounded-full bg-orange-100 text-orange-600 flex items-center justify-center text-[10px] font-black shrink-0 mt-0.5">
{i + 1}
</div>
<p className="text-[12px] text-gray-700 leading-relaxed">{reason}</p>
</div>
))}
</div>
</div>
)}
{/* Gitmeden önce */}
{placeDetail.tips?.length > 0 && (
<div className="space-y-2">
<h4 className="text-[11px] font-black text-gray-900 uppercase tracking-widest flex items-center gap-1.5">
<Lightbulb className="h-3.5 w-3.5 text-amber-500" />
Gitmeden önce bilin
</h4>
<div className="space-y-1.5 bg-amber-50 rounded-xl p-3 border border-amber-100">
{placeDetail.tips.map((tip, i) => (
<div key={i} className="flex items-start gap-2">
<span className="text-amber-500 mt-0.5 shrink-0 text-xs"></span>
<p className="text-[12px] text-gray-700 leading-relaxed">{tip}</p>
</div>
))}
</div>
</div>
)}
{/* Çalışma saatleri */}
{placeDetail.opening_hours?.length > 0 && (
<div className="space-y-2">
<h4 className="text-[11px] font-black text-gray-900 uppercase tracking-widest flex items-center gap-1.5">
<Clock className="h-3.5 w-3.5 text-blue-500" />
Çalışma Saatleri
</h4>
<div className="space-y-1">
{placeDetail.opening_hours.map((h, i) => (
<p key={i} className="text-[11px] text-gray-500">{h}</p>
))}
</div>
</div>
)}
</motion.div>
) : (
<motion.div
key="reviews"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
className="p-4 space-y-4"
>
{placeDetail.reviews?.length > 0 ? (
placeDetail.reviews.map((review, i) => (
<div key={i} className="space-y-2 pb-4 border-b border-gray-100 last:border-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-full bg-orange-100 flex items-center justify-center text-orange-600 font-black text-xs shrink-0">
{review.author?.[0]?.toUpperCase() || '?'}
</div>
<div>
<p className="text-xs font-bold text-gray-800">{review.author}</p>
<p className="text-[10px] text-gray-400">{review.time}</p>
</div>
</div>
<div className="flex items-center gap-0.5">
{[1,2,3,4,5].map(s => (
<Star key={s}
className={cn('h-3 w-3', s <= review.rating
? 'fill-amber-400 text-amber-400'
: 'text-gray-200 fill-gray-200'
)}
/>
))}
</div>
</div>
<p className="text-[12px] text-gray-600 leading-relaxed line-clamp-4">{review.text}</p>
</div>
))
) : (
<div className="flex flex-col items-center justify-center h-32 gap-2 text-gray-400">
<MessageSquare className="h-8 w-8 opacity-30" />
<p className="text-xs font-medium">Yorum bulunamadı</p>
</div>
)}
</motion.div>
)}
</AnimatePresence>
) : null}
</div>
{/* Adres + Plana ekle butonu */}
<div className="p-4 border-t bg-white shrink-0 space-y-3">
{selectedPOI.formatted_address && (
<p className="text-[11px] text-gray-400 leading-snug line-clamp-2">
📍 {selectedPOI.formatted_address}
</p>
)}
{onAddPlace && (
<Button
onClick={handleAdd}
disabled={added}
className={cn(
'w-full h-11 rounded-xl font-black text-sm gap-2 transition-all',
added
? 'bg-green-500 hover:bg-green-500 text-white'
: 'bg-orange-600 hover:bg-orange-700 text-white shadow-lg shadow-orange-200'
)}
>
{added ? (
<><CheckCircle2 className="h-4 w-4" /> Plana Eklendi!</>
) : (
<><Plus className="h-4 w-4" /> Güne Ekle</>
)}
</Button>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</>
)}
</div>