@@ -18,25 +18,17 @@ import { useEffect, useRef, useState, useMemo, useCallback } from 'react';
1818import { MapContainer , TileLayer , Marker , Polyline , Popup , useMap } from 'react-leaflet' ;
1919import type { MapPerson , MapData } from '@fsf/shared' ;
2020import { GeocodeProgressBar } from '../../map/GeocodeProgressBar' ;
21+ import { Link } from 'react-router-dom' ;
2122import {
2223 createPersonMarker ,
23- buildPopupHtml ,
2424 getMigrationLineStyle ,
2525 buildMigrationLines ,
2626 calculateBounds ,
2727} from '../../map/mapUtils' ;
2828
2929import 'leaflet/dist/leaflet.css' ;
3030
31- // Fix Leaflet default icon issue with Vite
3231import L from 'leaflet' ;
33- // @ts -expect-error Leaflet icon path fix for bundlers
34- delete L . Icon . Default . prototype . _getIconUrl ;
35- L . Icon . Default . mergeOptions ( {
36- iconRetinaUrl : 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png' ,
37- iconUrl : 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png' ,
38- shadowUrl : 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png' ,
39- } ) ;
4032
4133interface MigrationMapViewProps {
4234 mapData : MapData | null ;
@@ -67,6 +59,43 @@ function FitBounds({ persons }: { persons: MapPerson[] }) {
6759 return null ;
6860}
6961
62+ function PersonPopupContent ( { person, dbId } : { person : MapPerson ; dbId : string } ) {
63+ const genderIcon = person . gender === 'male' ? '\u2642' : person . gender === 'female' ? '\u2640' : '' ;
64+ const lineageLabel = person . lineage === 'paternal' ? 'Paternal' : person . lineage === 'maternal' ? 'Maternal' : '' ;
65+
66+ return (
67+ < div style = { { minWidth : 180 , maxWidth : 280 } } >
68+ { person . photoUrl && (
69+ < img
70+ src = { person . photoUrl }
71+ alt = { person . name }
72+ style = { { width : 48 , height : 48 , borderRadius : '50%' , objectFit : 'cover' , marginRight : 8 , float : 'left' } }
73+ />
74+ ) }
75+ < div >
76+ < Link
77+ to = { `/person/${ encodeURIComponent ( dbId ) } /${ encodeURIComponent ( person . id ) } ` }
78+ style = { { color : '#4A90D9' , fontWeight : 600 , fontSize : 14 , textDecoration : 'none' } }
79+ >
80+ { person . name } { person . isFavorite && ' \u2B50' }
81+ </ Link >
82+ < div style = { { color : '#888' , fontSize : 12 , marginTop : 2 } } >
83+ { genderIcon } { person . lifespan }
84+ { lineageLabel && ` \u00B7 ${ lineageLabel } ` }
85+ { person . generation > 0 && ` \u00B7 Gen ${ person . generation } ` }
86+ </ div >
87+ { ( person . birthPlace || person . deathPlace ) && (
88+ < div style = { { color : '#aaa' , fontSize : 11 , marginTop : 4 } } >
89+ { person . birthPlace && < div > Born: { person . birthPlace } </ div > }
90+ { person . deathPlace && < div > Died: { person . deathPlace } </ div > }
91+ </ div >
92+ ) }
93+ </ div >
94+ < div style = { { clear : 'both' } } />
95+ </ div >
96+ ) ;
97+ }
98+
7099export function MigrationMapView ( { mapData, dbId, loading, onReload } : MigrationMapViewProps ) {
71100 const [ timeRange , setTimeRange ] = useState < [ number , number ] > ( [ 0 , 2100 ] ) ;
72101 const [ showPaternal , setShowPaternal ] = useState ( true ) ;
@@ -277,7 +306,7 @@ export function MigrationMapView({ mapData, dbId, loading, onReload }: Migration
277306 icon = { createPersonMarker ( person ) }
278307 >
279308 < Popup >
280- < div dangerouslySetInnerHTML = { { __html : buildPopupHtml ( person , dbId ) } } />
309+ < PersonPopupContent person = { person } dbId = { dbId } />
281310 </ Popup >
282311 </ Marker >
283312 ) ;
0 commit comments