import { useRef, useState, useEffect, useCallback } from 'react' import { Plus } from 'lucide-react' import JourneyMap from './JourneyMap' import MobileEntryCard from './MobileEntryCard' import type { JourneyMapHandle } from './JourneyMap' import type { JourneyEntry } from '../../store/journeyStore' interface MapEntry { id: string lat: number lng: number title?: string | null mood?: string | null entry_date: string } interface Props { entries: JourneyEntry[] | any[] mapEntries: MapEntry[] trail?: { lat: number; lng: number }[] dark?: boolean readOnly?: boolean onEntryClick: (entry: any) => void onAddEntry?: () => void publicPhotoUrl?: (photoId: number) => string } export default function MobileMapTimeline({ entries, mapEntries, trail, dark, readOnly, onEntryClick, onAddEntry, publicPhotoUrl, }: Props) { const mapRef = useRef(null) const carouselRef = useRef(null) const [activeIndex, setActiveIndex] = useState(0) const cardRefs = useRef>(new Map()) // Sync map focus when carousel scrolls (with guard for uninitialized map) const syncMapToCarousel = useCallback((index: number) => { const entry = entries[index] if (!entry) return const mapEntry = mapEntries.find(m => String(m.id) === String(entry.id)) if (mapEntry) { try { mapRef.current?.focusMarker(String(mapEntry.id)) } catch {} } else { try { mapRef.current?.highlightMarker(null) } catch {} } }, [entries, mapEntries]) // IntersectionObserver for instant snap detection useEffect(() => { const el = carouselRef.current if (!el || entries.length === 0) return const observer = new IntersectionObserver( (observed) => { for (const o of observed) { if (o.isIntersecting) { const idx = Number(o.target.getAttribute('data-idx')) if (!isNaN(idx)) { setActiveIndex(idx) syncMapToCarousel(idx) } } } }, { root: el, threshold: 0.6 }, ) cardRefs.current.forEach(node => observer.observe(node)) return () => observer.disconnect() }, [entries.length, syncMapToCarousel]) // Scroll carousel to entry when map marker is clicked const handleMarkerClick = useCallback((id: string) => { const idx = entries.findIndex((e: any) => String(e.id) === id) if (idx === -1) return setActiveIndex(idx) const el = carouselRef.current if (!el) return const cardWidth = 272 el.scrollTo({ left: idx * cardWidth, behavior: 'smooth' }) }, [entries]) // Initial map focus — delay to let Leaflet initialize and fitBounds useEffect(() => { if (entries.length > 0) { const timer = setTimeout(() => syncMapToCarousel(0), 500) return () => clearTimeout(timer) } }, [entries.length]) const activeEntryId = entries[activeIndex] ? String(entries[activeIndex].id) : null if (entries.length === 0) { return (
{!readOnly && onAddEntry && (
)}
) } return (
{/* Full-screen map */} {/* Bottom carousel */}
{entries.map((entry: any, i: number) => (
{ if (node) cardRefs.current.set(i, node); else cardRefs.current.delete(i); }} style={{ scrollSnapAlign: 'center' }} > onEntryClick(entry)} publicPhotoUrl={publicPhotoUrl} />
))}
{/* FAB: add entry — top right */} {!readOnly && onAddEntry && (
)}
) }