"use client" import * as React from "react" import { ExternalLinkIcon, LocateFixedIcon, MapPinIcon, MinusIcon, PlusIcon } from "lucide-react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" export type MapMarker = { key: string label: React.ReactNode latitude: number longitude: number description?: React.ReactNode tone?: "neutral" | "info" | "success" | "warning" | "danger" | "muted" } export type MapViewProps = React.ComponentProps<"section"> & { center?: { latitude: number; longitude: number } zoom?: number markers?: MapMarker[] title?: React.ReactNode description?: React.ReactNode height?: number | string showMarkerList?: boolean showControls?: boolean iframeTitle?: string mapUrl?: string onMarkerSelect?: (marker: MapMarker) => void selectedMarkerKey?: string defaultSelectedMarkerKey?: string } function clampZoom(value: number) { return Math.max(1, Math.min(19, value)) } function toCoordinate(value: number) { return Number.isFinite(value) ? value : 0 } function getOpenStreetMapEmbedUrl(center: { latitude: number; longitude: number }, zoom: number, selected?: MapMarker) { const lat = toCoordinate(selected?.latitude ?? center.latitude) const lon = toCoordinate(selected?.longitude ?? center.longitude) const delta = 0.012 * (20 - clampZoom(zoom)) const bbox = [lon - delta, lat - delta, lon + delta, lat + delta].map((value) => value.toFixed(6)).join("%2C") return `https://www.openstreetmap.org/export/embed.html?bbox=${bbox}&layer=mapnik&marker=${lat.toFixed(6)}%2C${lon.toFixed(6)}` } function getOpenStreetMapUrl(center: { latitude: number; longitude: number }, zoom: number, selected?: MapMarker) { const lat = toCoordinate(selected?.latitude ?? center.latitude) const lon = toCoordinate(selected?.longitude ?? center.longitude) return `https://www.openstreetmap.org/?mlat=${lat.toFixed(6)}&mlon=${lon.toFixed(6)}#map=${clampZoom(zoom)}/${lat.toFixed(6)}/${lon.toFixed(6)}` } function MapView({ center = { latitude: 41.311081, longitude: 69.240562 }, zoom = 13, markers = [], title = "Map", description, height = 420, showMarkerList = true, showControls = true, iframeTitle = "Interactive map", mapUrl, onMarkerSelect, selectedMarkerKey, defaultSelectedMarkerKey, className, ...props }: MapViewProps) { const [internalZoom, setInternalZoom] = React.useState(clampZoom(zoom)) const [internalSelectedKey, setInternalSelectedKey] = React.useState(defaultSelectedMarkerKey ?? markers[0]?.key) const activeKey = selectedMarkerKey ?? internalSelectedKey const selectedMarker = markers.find((marker) => marker.key === activeKey) const resolvedUrl = mapUrl ?? getOpenStreetMapEmbedUrl(center, internalZoom, selectedMarker) const externalUrl = getOpenStreetMapUrl(center, internalZoom, selectedMarker) React.useEffect(() => { setInternalZoom(clampZoom(zoom)) }, [zoom]) const selectMarker = (marker: MapMarker) => { if (selectedMarkerKey === undefined) setInternalSelectedKey(marker.key) onMarkerSelect?.(marker) } return (
{title ?

{title}

: null} {description ?

{description}

: null}
{showControls ? (
{internalZoom}x
) : null}