admin/components/map/MapirPreviewMap.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

94 lines
2.4 KiB
TypeScript

'use client'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import MarkerIcon from '@/components/icons/MarkerIcon'
import { getMapirMapComponent, MAPIR_STYLE_URL } from '@/components/map/mapirMapFactory'
import { getPublicMapApiKey, toMapirLngLat } from '@/helpers/mapir'
interface MapirPreviewMapProps {
lat: number
lng: number
zoom?: number
scrollZoom?: boolean
}
interface MapLike {
resize: () => void
}
const DEFAULT_ZOOM = 13
const MapirPreviewMap = ({ lat, lng, zoom = DEFAULT_ZOOM, scrollZoom = false }: MapirPreviewMapProps) => {
const apiKey = getPublicMapApiKey()
const Map = useMemo(() => {
const k = apiKey.trim()
if (!k) return null
return getMapirMapComponent(k)
}, [apiKey])
const center = useMemo(() => toMapirLngLat(lat, lng), [lat, lng])
const zoomArr = useMemo(() => [zoom] as [number], [zoom])
const mapRef = useRef<MapLike | null>(null)
const rootRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
const root = rootRef.current
if (!root || typeof ResizeObserver === 'undefined') return
const resize = () => {
mapRef.current?.resize()
}
const observer = new ResizeObserver(resize)
observer.observe(root)
const timer = window.setTimeout(resize, 320)
return () => {
observer.disconnect()
window.clearTimeout(timer)
}
}, [])
const onStyleLoad = useCallback((map: MapLike) => {
mapRef.current = map
map.resize()
}, [])
if (!Map) {
return null
}
// Map.ir Map is memoized/cached by API key in getMapirMapComponent — not recreated each render.
/* eslint-disable react-hooks/static-components -- Map component comes from mapir setToken cache */
return (
<div
ref={rootRef}
className="relative h-full w-full min-h-[240px]"
>
<Map
center={center}
containerStyle={{ height: '100%', position: 'relative', width: '100%' }}
maxZoom={22}
minZoom={0}
movingMethod="jumpTo"
scrollZoom={scrollZoom}
style={MAPIR_STYLE_URL}
zoom={zoomArr}
onStyleLoad={onStyleLoad}
/>
<div
aria-hidden
className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center"
>
<MarkerIcon className="size-9 -translate-y-1/2 text-primary drop-shadow-md" />
</div>
</div>
)
/* eslint-enable react-hooks/static-components */
}
export default MapirPreviewMap