Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
94 lines
2.4 KiB
TypeScript
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
|