import { useEffect, useMemo, useState } from 'react'; import { MapContainer, TileLayer, Marker, Popup, useMap, Polyline } from 'react-leaflet'; import L from 'leaflet'; import { Place, GeoPosition } from '../types'; import scheduleData from '../data/schedule.json'; import 'leaflet/dist/leaflet.css'; interface MapPageProps { places: Place[]; position: GeoPosition | null; selectedPlace: Place | null; onSelectPlace: (place: Place) => void; showRouteForDay: number | null; onClearRoute: () => void; } // Fix marker icons delete (L.Icon.Default.prototype as any)._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon-2x.png', iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-icon.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png', }); const createIcon = (emoji: string, isSelected: boolean = false) => { return L.divIcon({ className: 'custom-marker', html: `
${emoji}
`, iconSize: [40, 40], iconAnchor: [20, 20], }); }; const getCategoryEmoji = (category: string): string => { const emojis: Record = { sight: '๐Ÿฏ', restaurant: '๐Ÿœ', coffee: 'โ˜•', snack: '๐Ÿก', hotel: '๐Ÿจ', }; return emojis[category] || '๐Ÿ“'; }; function MapController({ center, zoom }: { center: [number, number]; zoom: number }) { const map = useMap(); useEffect(() => { map.flyTo(center, zoom, { duration: 0.5 }); }, [center, zoom, map]); return null; } function LocationMarker({ position }: { position: GeoPosition | null }) { if (!position) return null; return ( `, iconSize: [22, 22], iconAnchor: [11, 11], })} /> ); } export function MapPage({ places, position, selectedPlace, onSelectPlace, showRouteForDay, onClearRoute }: MapPageProps) { const [mapCenter, setMapCenter] = useState<[number, number]>([35.6762, 139.6503]); const [mapZoom, setMapZoom] = useState(11); const schedule = scheduleData as Record; // Get route for selected day const routeCoordinates = useMemo(() => { if (!showRouteForDay) return []; const dayData = schedule[`day${showRouteForDay}`]; if (!dayData) return []; const coords: [number, number][] = []; dayData.schedule.forEach((item: any) => { if (item.placeId) { const place = places.find(p => p.id === item.placeId); if (place) { coords.push([place.lat, place.lng]); } } }); return coords; }, [showRouteForDay, places, schedule]); // Update center when selected place changes useEffect(() => { if (selectedPlace) { setMapCenter([selectedPlace.lat, selectedPlace.lng]); setMapZoom(15); } }, [selectedPlace]); // Update center for route useEffect(() => { if (routeCoordinates.length > 0) { const bounds = L.latLngBounds(routeCoordinates); const center = bounds.getCenter(); setMapCenter([center.lat, center.lng]); setMapZoom(12); } }, [routeCoordinates]); const visiblePlaces = useMemo(() => { if (showRouteForDay) { const dayData = schedule[`day${showRouteForDay}`]; if (!dayData) return places; const placeIds = dayData.schedule .filter((item: any) => item.placeId) .map((item: any) => item.placeId); return places.filter(p => placeIds.includes(p.id)); } return places; }, [places, showRouteForDay, schedule]); return (
{/* Route polyline */} {routeCoordinates.length > 1 && ( )} {/* Place markers */} {visiblePlaces.map((place) => ( onSelectPlace(place), }} >

{place.name}

{place.nameJp}

{place.day && ( ะ”ะตะฝัŒ {place.day} )}
))}
{/* Map controls */}
{position && ( )}
{/* Route info bar */} {showRouteForDay && (

ะœะฐั€ัˆั€ัƒั‚ ะดะฝั {showRouteForDay}

{visiblePlaces.length} ะผะตัั‚

)}
); }