'use client' import { useCallback, useEffect, useRef, useState } from 'react' import dynamic from 'next/dynamic' import ConsumerModal from '@/components/consumer/ConsumerModal' import ConsumerPickerTrigger from '@/components/events/create/ConsumerPickerTrigger' import CloseLinearIcon from '@/components/icons/CloseLinearIcon' import MapIcon from '@/components/icons/MapIcon' import { texts } from '@/texts' import { addToast } from '@/lib/toast' import { cn } from '@/lib/cn' import { getPublicMapApiKey } from '@/helpers/mapir' import { reverseGeocodeMapIr } from '@/services/mapirReverseGeocode' const MapirLocationPicker = dynamic(() => import('@/components/map/MapirLocationPicker'), { ssr: false }) interface MapLocationPickerProps { lat: number lng: number onChange: (coords: { lat: number; lng: number }) => void /** پر کردن خودکار آدرس از reverse geocode؛ کاربر همچنان می‌تواند آدرس را ویرایش کند */ onAddressResolved?: (address: string) => void disabled?: boolean /** Consumer create uses a bottom sheet; admin/edit keep the inline map. */ mode?: 'inline' | 'modal' /** Optional address shown on the modal trigger once a place is chosen. */ displayAddress?: string } const TEHRAN_LAT = 35.6892 const TEHRAN_LNG = 51.389 function MapCanvas({ centerLat, centerLng, markerLat, markerLng, disabled, mapApiKey, isClient, className, mapHeight = 320, onPick, }: { centerLat: number centerLng: number markerLat: number | null markerLng: number | null disabled?: boolean mapApiKey: string isClient: boolean className?: string /** ارتفاع صریح پیکسلی — درصد داخل مودال اغلب ۰ می‌شود و مارکر را خراب می‌کند */ mapHeight?: number onPick: (lat: number, lng: number) => void }) { return (
{isClient && mapApiKey ? ( ) : isClient && !mapApiKey ? (
{texts.events.mapKeyMissing}
) : (
{texts.events.mapLoading}
)}
) } export default function MapLocationPicker({ lat, lng, onChange, onAddressResolved, disabled, mode = 'inline', displayAddress, }: MapLocationPickerProps) { const [isClient, setIsClient] = useState(false) const [isOpen, setIsOpen] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const [markerPosition, setMarkerPosition] = useState<[number, number] | null>(null) const [draftPosition, setDraftPosition] = useState<[number, number] | null>(null) const mapApiKey = getPublicMapApiKey() const reverseRequestIdRef = useRef(0) useEffect(() => { setIsClient(true) }, []) useEffect(() => { if (Number.isFinite(lat) && Number.isFinite(lng)) { setMarkerPosition([lat, lng]) } }, [lat, lng]) const hasValidCoords = Number.isFinite(lat) && Number.isFinite(lng) const mapCenterLat = hasValidCoords ? lat : TEHRAN_LAT const mapCenterLng = hasValidCoords ? lng : TEHRAN_LNG const reverseGeocode = useCallback( async (pickedLat: number, pickedLng: number) => { const key = mapApiKey.trim() if (!key) return const rid = ++reverseRequestIdRef.current try { const address = await reverseGeocodeMapIr(pickedLat, pickedLng, key) if (rid !== reverseRequestIdRef.current) return if (address) { onAddressResolved?.(address) } else { addToast({ title: texts.events.mapAddressNotFound, color: 'warning' }) } } catch (err) { if (rid !== reverseRequestIdRef.current) return const message = err instanceof Error ? err.message : texts.validation.map.mapAddressFailed addToast({ title: message, color: 'danger' }) } }, [mapApiKey, onAddressResolved] ) const handleInlineMapClick = useCallback( async (pickedLat: number, pickedLng: number) => { if (disabled) return setMarkerPosition([pickedLat, pickedLng]) onChange({ lat: pickedLat, lng: pickedLng }) await reverseGeocode(pickedLat, pickedLng) }, [disabled, onChange, reverseGeocode] ) const openModal = () => { if (disabled) return setDraftPosition(markerPosition ?? (hasValidCoords ? [lat, lng] : null)) setIsOpen(true) } const acceptModal = async () => { if (!draftPosition) return const [pickedLat, pickedLng] = draftPosition setIsSubmitting(true) try { setMarkerPosition(draftPosition) onChange({ lat: pickedLat, lng: pickedLng }) await reverseGeocode(pickedLat, pickedLng) setIsOpen(false) } finally { setIsSubmitting(false) } } const triggerLabel = displayAddress?.trim() ? displayAddress.trim() : markerPosition ? texts.events.mapLocationSelected : texts.events.mapSelectModalTitle if (mode === 'modal') { // مرکز نقشه را روی pick عوض نکن — فقط مارکر جابه‌جا شود (jumpTo روی هر کلیک مارکر را از دست می‌دهد/پنهان می‌کند) return (

{texts.events.mapTitle}

{triggerLabel}

{disabled ? texts.events.mapLocked : texts.events.mapModalHint}

} cancelLabel={texts.common.cancel} isOpen={isOpen} isSubmitting={isSubmitting} submitDisabled={!draftPosition} submitLabel={texts.common.select} title={texts.events.mapSelectModalTitle} onOpenChange={setIsOpen} onSubmit={() => { void acceptModal() }} > {isOpen ? ( { if (disabled || isSubmitting) return setDraftPosition([pickedLat, pickedLng]) }} /> ) : null}
) } return (

{texts.events.mapTitle}

{disabled ? texts.events.mapLocked : texts.events.mapHint}

) }