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} ะผะตัั
)}
);
}