import { useEffect, useState, useCallback, useRef } from "react"; import { useTranslation } from "react-i18next"; import { Badge } from "@trails-cool/ui"; import type { YjsState } from "~/lib/use-yjs"; import type { DayStage } from "@trails-cool/gpx"; import { setOvernight } from "~/lib/overnight"; import { DayBreakdown } from "./DayBreakdown"; import { useNearbyPois } from "~/lib/use-nearby-pois"; import { poiCategories } from "@trails-cool/map-core"; import type { Poi } from "~/lib/pois"; import { extractWaypointData, waypointFromYMap, waypointToYMap, type WaypointData, } from "~/lib/waypoint-ymap"; const NOTE_MAX = 500; interface WaypointSidebarProps { yjs: YjsState; sessionId: string; routeStats?: { distance?: number; elevationGain?: number; elevationLoss?: number; }; days: DayStage[]; onWaypointHover?: (index: number | null) => void; onWaypointSelect?: (index: number | null) => void; } export function WaypointSidebar({ yjs, sessionId, routeStats, days, onWaypointHover, onWaypointSelect }: WaypointSidebarProps) { const { t } = useTranslation("planner"); const [waypoints, setWaypoints] = useState([]); const [selectedIndex, setSelectedIndex] = useState(null); const [editingNoteIndex, setEditingNoteIndex] = useState(null); const [noteEditValue, setNoteEditValue] = useState(""); const textareaRef = useRef(null); useEffect(() => { const update = () => setWaypoints(extractWaypointData(yjs.waypoints)); yjs.waypoints.observeDeep(update); update(); return () => yjs.waypoints.unobserveDeep(update); }, [yjs.waypoints]); const deleteWaypoint = useCallback( (index: number) => { yjs.doc.transact(() => yjs.waypoints.delete(index, 1), "local"); }, [yjs.doc, yjs.waypoints], ); const moveWaypoint = useCallback( (from: number, to: number) => { if (from === to || from < 0 || to < 0) return; const item = yjs.waypoints.get(from); if (!item) return; const wp = waypointFromYMap(item); yjs.doc.transact(() => { yjs.waypoints.delete(from, 1); yjs.waypoints.insert(to, [waypointToYMap(wp)]); }, "local"); }, [yjs.waypoints, yjs.doc], ); const toggleOvernight = useCallback( (index: number) => { const wp = waypoints[index]; if (!wp) return; setOvernight(yjs, index, !wp.overnight); }, [yjs, waypoints], ); const setNote = useCallback( (index: number, note: string) => { const yMap = yjs.waypoints.get(index); if (!yMap) return; yjs.doc.transact(() => { if (note.trim()) { yMap.set("note", note.trim()); } else { yMap.delete("note"); } }, "local"); }, [yjs], ); const selectWaypoint = useCallback( (index: number | null) => { setSelectedIndex(index); onWaypointSelect?.(index); }, [onWaypointSelect], ); const openNoteEditor = useCallback( (index: number) => { setNoteEditValue(waypoints[index]?.note ?? ""); setEditingNoteIndex(index); setTimeout(() => { textareaRef.current?.focus(); // auto-resize if (textareaRef.current) { textareaRef.current.style.height = "auto"; textareaRef.current.style.height = textareaRef.current.scrollHeight + "px"; } }, 0); }, [waypoints], ); const snapToPoi = useCallback( (poi: Poi) => { if (selectedIndex === null) return; const yMap = yjs.waypoints.get(selectedIndex); if (!yMap) return; const cat = poiCategories.find((c) => c.id === poi.category); const notePrefix = cat ? `${cat.icon} ${poi.name ?? cat.id}` : (poi.name ?? ""); yjs.doc.transact(() => { yMap.set("lat", poi.lat); yMap.set("lon", poi.lon); if (poi.name && !yMap.get("name")) yMap.set("name", poi.name); const existing = yMap.get("note") as string | undefined; yMap.set("note", existing ? `${notePrefix}\n${existing}` : notePrefix); if (poi.id) yMap.set("osmId", poi.id); }, "local"); }, [selectedIndex, yjs, poiCategories], ); const selectedWp = selectedIndex !== null ? waypoints[selectedIndex] : undefined; const nearbyPoisState = useNearbyPois(selectedWp?.lat, selectedWp?.lon, sessionId); const [showAllNearby, setShowAllNearby] = useState(false); const hasMultipleDays = days.length > 1; const renderWaypointRow = (wp: WaypointData, i: number) => (
  • selectWaypoint(selectedIndex === i ? null : i)} onMouseEnter={() => onWaypointHover?.(i)} onMouseLeave={() => onWaypointHover?.(null)} > {i + 1}

    {wp.name ?? `${wp.lat.toFixed(4)}, ${wp.lon.toFixed(4)}`}

    {/* Note display / editor */} {editingNoteIndex === i ? (
    e.stopPropagation()}>