add geo distance to header

This commit is contained in:
Anton Tranelis 2025-09-12 23:24:26 +02:00
parent a8358d96a7
commit 84dd0cee01
2 changed files with 82 additions and 6 deletions

View File

@ -10,7 +10,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon'
import { MapPinIcon, ShareIcon, UserPlusIcon } from '@heroicons/react/24/outline'
import { MapPinIcon, ShareIcon } from '@heroicons/react/24/outline'
import PencilIcon from '@heroicons/react/24/solid/PencilIcon'
import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
import { useState } from 'react'
@ -20,7 +20,9 @@ import { useNavigate } from 'react-router-dom'
import TargetDotSVG from '#assets/targetDot.svg'
import { useAppState } from '#components/AppShell/hooks/useAppState'
import { useGeoDistance } from '#components/Map/hooks/useGeoDistance'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import { useGetItemTags } from '#components/Map/hooks/useTags'
import DialogModal from '#components/Templates/DialogModal'
import type { Item } from '#types/Item'
@ -56,8 +58,9 @@ export function HeaderView({
const hasUserPermission = useHasUserPermission()
const navigate = useNavigate()
const appState = useAppState()
const getItemTags = useGetItemTags()
const [imageLoaded, setImageLoaded] = useState(false)
const { distance } = useGeoDistance(item?.position ?? undefined)
const avatar =
(item?.image && appState.assetsApi.url + item.image + '?width=160&heigth=160') ||
@ -69,6 +72,11 @@ export function HeaderView({
const params = new URLSearchParams(window.location.search)
const formatDistance = (dist: number | null): string | null => {
if (!dist) return null
return dist < 10 ? `${dist.toFixed(1)} km` : `${Math.round(dist)} km`
}
const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => {
setModalOpen(true)
event.stopPropagation()
@ -196,13 +204,28 @@ export function HeaderView({
</div>
{big && (
<div className='tw:flex tw:row tw:mt-2 '>
<div className='tw:w-16 tw:text-center tw:font-bold tw:text-primary '></div>
<div className='tw:w-16 tw:text-center tw:font-bold tw:text-primary '>
{' '}
{formatDistance(distance) && (
<span
style={{
color: `${item?.color ?? (item && (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : (item?.layer?.markerDefaultColor ?? '#000')))}`,
}}
>
{formatDistance(distance)}
</span>
)}
</div>
<div className='tw:grow'></div>
<div className=''>
<div className='tw:btn tw:btn-sm tw:btn-primary tw:mr-2'>
<UserPlusIcon className='tw:w-4' />
<button
style={{
backgroundColor: `${item?.color ?? (item && (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : (item?.layer?.markerDefaultColor ?? '#000')))}`,
}}
className='tw:btn tw:text-white tw:btn-sm tw:mr-2 '
>
Follow
</div>
</button>
<div className='tw:btn tw:btn-sm tw:mr-2 tw:px-2'>
<LuNavigation className='tw:h-4 tw:w-4' />
</div>

View File

@ -0,0 +1,53 @@
import { useState, useEffect } from 'react'
import { useMyProfile } from './useMyProfile'
import type { Point } from 'geojson'
const getDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {
const R = 6371 // Earth's radius in km
const dLat = ((lat2 - lat1) * Math.PI) / 180
const dLon = ((lon2 - lon1) * Math.PI) / 180
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c // Distance in km
}
export const useGeoDistance = (targetPoint?: Point) => {
const [distance, setDistance] = useState<number | null>(null)
const [error, setError] = useState<string | null>(null)
const { myProfile, isMyProfileLoaded } = useMyProfile()
useEffect(() => {
setError(null)
setDistance(null)
if (!isMyProfileLoaded) {
return
}
if (!myProfile?.position || !targetPoint) {
setError('Missing location data')
return
}
try {
const userGeoJson = myProfile.position
const [userLon, userLat] = userGeoJson.coordinates
const [targetLon, targetLat] = targetPoint.coordinates
const dist = getDistance(userLat, userLon, targetLat, targetLon)
setDistance(dist)
} catch (err) {
setError('Calculation error')
throw err
}
}, [myProfile, isMyProfileLoaded, targetPoint])
return { distance, error, userLocation: myProfile?.position as Point | undefined }
}