import { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import type { MapData } from './types'; // Leaflet CDN を動的にロードする let leafletLoaded = false; let leafletLoadPromise: Promise | null = null; function loadLeaflet(): Promise { if (leafletLoaded) return Promise.resolve(); if (leafletLoadPromise) return leafletLoadPromise; leafletLoadPromise = new Promise((resolve, reject) => { // CSS const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; document.head.appendChild(link); // JS const script = document.createElement('script'); script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; script.onload = () => { leafletLoaded = true; resolve(); }; script.onerror = () => reject(new Error('Failed to load Leaflet')); document.head.appendChild(script); }); return leafletLoadPromise; } declare const L: typeof import('leaflet'); export function MapPlacesDetail({ data }: { data: MapData }) { const { t } = useTranslation('embed'); const { query, places } = data; const mapRef = useRef(null); const mapInstanceRef = useRef(null); useEffect(() => { if (!mapRef.current || places.length === 0) return; let cancelled = false; loadLeaflet().then(() => { if (cancelled || !mapRef.current) return; // 既存のマップがあれば破棄 if (mapInstanceRef.current) { mapInstanceRef.current.remove(); } const center = { lat: places[0]!.lat, lon: places[0]!.lon }; const map = L.map(mapRef.current).setView([center.lat, center.lon], 13); mapInstanceRef.current = map; L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19, }).addTo(map); const markers = places.map((p) => L.marker([p.lat, p.lon]) .addTo(map) .bindPopup(`${p.name}
${p.address}`), ); if (markers.length > 1) { const group = L.featureGroup(markers); map.fitBounds(group.getBounds().pad(0.15)); } // モーダルが開いた直後はサイズが確定していない場合がある setTimeout(() => map.invalidateSize(), 100); }); return () => { cancelled = true; if (mapInstanceRef.current) { mapInstanceRef.current.remove(); mapInstanceRef.current = null; } }; }, [places]); return (

📍 {t('searchResults.map', { query })}

{/* Leaflet map */}
{/* Place list */}
{places.map((p, i) => (
#{i + 1}

{p.name}

📍 {p.address}
📌 {p.lat.toFixed(6)}, {p.lon.toFixed(6)}
{p.type &&
🏷 {p.type}
} {p.details &&
💬 {p.details}
}
{t('viewOn.osm')}
))}
); }