Add route detail day segment highlighting and per-day GPX export

- Hovering a day row dims other segments (opacity 0.3) and thickens
  the hovered day's segment (weight 5) for clear visual focus
- Each day row has a GPX download button that exports just that day's
  track segment via /api/routes/:id/gpx?day=N
- GPX endpoint uses computeDays to extract the correct track slice

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-11 00:44:11 +02:00
parent e2fc682a95
commit 7029c85dd0
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
3 changed files with 101 additions and 14 deletions

View file

@ -26,9 +26,11 @@ interface RouteMapProps {
interactive?: boolean;
className?: string;
dayBreaks?: number[];
/** 1-based day number to highlight, or null for no highlight */
highlightedDay?: number | null;
}
export function RouteMapThumbnail({ geojson, interactive, className, dayBreaks }: RouteMapProps) {
export function RouteMapThumbnail({ geojson, interactive, className, dayBreaks, highlightedDay }: RouteMapProps) {
const data: GeoJsonObject = JSON.parse(geojson);
return (
@ -48,7 +50,7 @@ export function RouteMapThumbnail({ geojson, interactive, className, dayBreaks }
attribution={interactive ? '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' : undefined}
/>
{dayBreaks && dayBreaks.length > 0 ? (
<DayColoredRoute data={data} dayBreaks={dayBreaks} />
<DayColoredRoute data={data} dayBreaks={dayBreaks} highlightedDay={highlightedDay} />
) : (
<GeoJSON data={data} style={{ color: "#2563eb", weight: 3, opacity: 0.8 }} />
)}
@ -57,7 +59,7 @@ export function RouteMapThumbnail({ geojson, interactive, className, dayBreaks }
);
}
function DayColoredRoute({ data, dayBreaks }: { data: GeoJsonObject; dayBreaks: number[] }) {
function DayColoredRoute({ data, dayBreaks, highlightedDay }: { data: GeoJsonObject; dayBreaks: number[]; highlightedDay?: number | null }) {
// Extract coordinates from the GeoJSON LineString
const geometry = (data as { type: string; coordinates?: number[][] }).coordinates
?? ((data as { features?: Array<{ geometry: { coordinates: number[][] } }> }).features?.[0]?.geometry?.coordinates);
@ -84,9 +86,13 @@ function DayColoredRoute({ data, dayBreaks }: { data: GeoJsonObject; dayBreaks:
});
}
const isHighlighting = highlightedDay != null;
return (
<>
{segments.map((seg, i) => {
const dayNum = i + 1;
const isActive = highlightedDay === dayNum;
const segData: GeoJsonObject = {
type: "Feature",
geometry: { type: "LineString", coordinates: seg.coords },
@ -94,9 +100,13 @@ function DayColoredRoute({ data, dayBreaks }: { data: GeoJsonObject; dayBreaks:
} as unknown as GeoJsonObject;
return (
<GeoJSON
key={i}
key={`${i}-${highlightedDay}`}
data={segData}
style={{ color: seg.color, weight: 3, opacity: 0.8 }}
style={{
color: seg.color,
weight: isHighlighting ? (isActive ? 5 : 2) : 3,
opacity: isHighlighting ? (isActive ? 1 : 0.3) : 0.8,
}}
/>
);
})}