Add map previews to journal route and activity pages
- Route/activity list pages: map thumbnails with route drawn on OSM tiles - Route/activity detail pages: interactive Leaflet map with zoom controls - Server: expose GeoJSON from PostGIS via ST_AsGeoJSON (simplified for lists) - RouteMapThumbnail component: shared, supports thumbnail and interactive modes - Placeholder shown for routes/activities without geometry - Archive journal-route-previews change, sync specs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a4af698ca
commit
6aac8bd885
16 changed files with 399 additions and 39 deletions
51
apps/journal/app/components/RouteMapThumbnail.tsx
Normal file
51
apps/journal/app/components/RouteMapThumbnail.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { MapContainer, TileLayer, GeoJSON, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import type { GeoJsonObject } from "geojson";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
function FitBounds({ data }: { data: GeoJsonObject }) {
|
||||
const map = useMap();
|
||||
const fitted = useRef(false);
|
||||
useEffect(() => {
|
||||
if (fitted.current) return;
|
||||
const layer = L.geoJSON(data);
|
||||
const bounds = layer.getBounds();
|
||||
if (bounds.isValid()) {
|
||||
map.fitBounds(bounds, { padding: [20, 20] });
|
||||
fitted.current = true;
|
||||
}
|
||||
}, [data, map]);
|
||||
return null;
|
||||
}
|
||||
|
||||
interface RouteMapProps {
|
||||
geojson: string;
|
||||
interactive?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function RouteMapThumbnail({ geojson, interactive, className }: RouteMapProps) {
|
||||
const data: GeoJsonObject = JSON.parse(geojson);
|
||||
|
||||
return (
|
||||
<MapContainer
|
||||
center={[50, 10]}
|
||||
zoom={6}
|
||||
className={className ?? "h-36 w-full rounded"}
|
||||
zoomControl={interactive ?? false}
|
||||
attributionControl={interactive ?? false}
|
||||
dragging={interactive ?? false}
|
||||
scrollWheelZoom={interactive ?? false}
|
||||
doubleClickZoom={interactive ?? false}
|
||||
touchZoom={interactive ?? false}
|
||||
>
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution={interactive ? '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' : undefined}
|
||||
/>
|
||||
<GeoJSON data={data} style={{ color: "#2563eb", weight: 3, opacity: 0.8 }} />
|
||||
<FitBounds data={data} />
|
||||
</MapContainer>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue