104 lines
3.0 KiB
TypeScript
104 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef } from 'react';
|
|
import L from 'leaflet';
|
|
import { ExternalLink, MapPin } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
let iconsFixed = false;
|
|
|
|
function fixLeafletIcons() {
|
|
if (iconsFixed || typeof window === 'undefined') return;
|
|
iconsFixed = true;
|
|
L.Icon.Default.mergeOptions({
|
|
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
|
|
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
|
|
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
popupAnchor: [1, -34],
|
|
shadowSize: [41, 41]
|
|
});
|
|
}
|
|
|
|
function buildMapsUrl(latitude: number, longitude: number) {
|
|
return `https://yandex.ru/maps/?pt=${longitude},${latitude}&z=16&l=map`;
|
|
}
|
|
|
|
export function ChatLocationCard({
|
|
latitude,
|
|
longitude,
|
|
address,
|
|
label,
|
|
variant = 'theirs'
|
|
}: {
|
|
latitude: number;
|
|
longitude: number;
|
|
address?: string;
|
|
label?: string;
|
|
variant?: 'mine' | 'theirs';
|
|
}) {
|
|
const mapRef = useRef<HTMLDivElement>(null);
|
|
const mapInstance = useRef<L.Map | null>(null);
|
|
|
|
const title = label?.trim() || address?.trim() || `${latitude.toFixed(5)}, ${longitude.toFixed(5)}`;
|
|
const mapsUrl = buildMapsUrl(latitude, longitude);
|
|
|
|
useEffect(() => {
|
|
fixLeafletIcons();
|
|
if (!mapRef.current || mapInstance.current) return;
|
|
|
|
const map = L.map(mapRef.current, {
|
|
zoomControl: false,
|
|
attributionControl: true,
|
|
dragging: false,
|
|
scrollWheelZoom: false,
|
|
doubleClickZoom: false,
|
|
boxZoom: false,
|
|
keyboard: false,
|
|
touchZoom: false
|
|
}).setView([latitude, longitude], 15);
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap'
|
|
}).addTo(map);
|
|
|
|
L.marker([latitude, longitude]).addTo(map);
|
|
mapInstance.current = map;
|
|
|
|
const resizeObserver = new ResizeObserver(() => {
|
|
map.invalidateSize();
|
|
});
|
|
resizeObserver.observe(mapRef.current);
|
|
|
|
return () => {
|
|
resizeObserver.disconnect();
|
|
map.remove();
|
|
mapInstance.current = null;
|
|
};
|
|
}, [latitude, longitude]);
|
|
|
|
return (
|
|
<a
|
|
href={mapsUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
data-chat-interactive="true"
|
|
className={cn(
|
|
'block w-[min(100%,280px)] overflow-hidden rounded-2xl border transition hover:shadow-md',
|
|
variant === 'mine' ? 'border-[#7ea06a]/35 bg-white' : 'border-[#dce3ec] bg-white'
|
|
)}
|
|
>
|
|
<div ref={mapRef} className="h-[160px] w-full" />
|
|
<div className="flex items-start gap-2 px-3 py-2.5">
|
|
<MapPin className={cn('mt-0.5 h-4 w-4 shrink-0', variant === 'mine' ? 'text-[#7ea06a]' : 'text-[#3390ec]')} />
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-medium text-[#1f2430]">{title}</p>
|
|
{address && label ? <p className="truncate text-xs text-[#667085]">{address}</p> : null}
|
|
</div>
|
|
<ExternalLink className="mt-0.5 h-4 w-4 shrink-0 text-[#667085]" />
|
|
</div>
|
|
</a>
|
|
);
|
|
}
|