Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/components/maps/GlobalMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ export const GlobalMap: React.FC<GlobalMapProps> = ({
}
}, [mapInstance, isSourceLoaded, initialAreaId, findAreaById])

const [allCrags, setAllCrags] = useState<ActiveFeature[]>([])
useEffect(() => {
if (!mapInstance) return
const features = mapInstance.querySourceFeatures('crags', { sourceLayer: 'crags' })
const cragsFeatures = features
.map(f => tileToFeature('crag-name-labels', {x:0,y:0}, f.geometry, f.properties as TileProps, mapInstance))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use var name as "feature" rather than "f"

Comment on lines +219 to +221
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic values {x:0, y:0} are passed to tileToFeature without explanation. Consider extracting this as a named constant (e.g., DEFAULT_TILE_COORDS) or adding a comment explaining why these coordinates are used when querying all crags.

Copilot uses AI. Check for mistakes.
.filter((f): f is ActiveFeature => f !== null) // фильтруем null и сохраняем тип
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover comment.

Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is written in Russian ('фильтруем null и сохраняем тип' means 'filter null and preserve type'). Project comments should be in English for consistency. Replace with: 'filter out null values and preserve type'.

Suggested change
.filter((f): f is ActiveFeature => f !== null) // фильтруем null и сохраняем тип
.filter((f): f is ActiveFeature => f !== null) // filter out null values and preserve type

Copilot uses AI. Check for mistakes.
setAllCrags(cragsFeatures)
}, [mapInstance])

const highlightCrag = (crag: ActiveFeature) => {
setClickInfo(prev => {
if (prev) setActiveFeatureVisual(prev, { selected: false, hover: false })
setActiveFeatureVisual(crag, { selected: true, hover: false })
return crag
})
Comment on lines +227 to +231
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setClickInfo callback is using the previous state incorrectly. The callback receives prev but doesn't use it in the return logic - it always returns crag regardless of the previous value. This should likely be a direct call: setClickInfo(crag) followed by the visual state updates, or the callback should properly handle the state transition.

Suggested change
setClickInfo(prev => {
if (prev) setActiveFeatureVisual(prev, { selected: false, hover: false })
setActiveFeatureVisual(crag, { selected: true, hover: false })
return crag
})
if (clickInfo) setActiveFeatureVisual(clickInfo, { selected: false, hover: false })
setActiveFeatureVisual(crag, { selected: true, hover: false })
setClickInfo(crag)

Copilot uses AI. Check for mistakes.
const [lng, lat] = crag.geometry.coordinates as [number, number]
mapInstance?.flyTo({ center: [lng, lat], zoom: 15 })
}

return (
<div className='relative w-full h-full'>
<Map
Expand All @@ -235,7 +255,12 @@ export const GlobalMap: React.FC<GlobalMapProps> = ({
cooperativeGestures={showFullscreenControl}
interactiveLayerIds={['crag-markers', 'crag-name-labels', 'area-boundaries', 'organizations']}
>
<MapToolbar layerState={dataLayersDisplayState} onChange={setDataLayersDisplayState} />
<MapToolbar
layerState={dataLayersDisplayState}
onChange={setDataLayersDisplayState}
cragsList={allCrags}
onSelectCrag={highlightCrag}
/>
<MapLayersSelector emit={updateMapLayer} />
<ScaleControl unit='imperial' style={{ marginBottom: 10 }} position='bottom-left' />
<ScaleControl unit='metric' style={{ marginBottom: 0 }} position='bottom-left' />
Expand All @@ -262,3 +287,4 @@ export const LazyGlobalMap = dynamic<GlobalMapProps>(async () => await import('.
module => module.GlobalMap), {
ssr: false
})

61 changes: 53 additions & 8 deletions src/components/maps/MapToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
import { ChangeEventHandler } from 'react'
import { ChangeEventHandler, ChangeEvent, useState } from 'react'
import { DataLayersDisplayState } from './GlobalMap'
import { ActiveFeature } from './TileTypes'

export interface MapToolbarProps {
interface MapToolbarProps {
layerState: DataLayersDisplayState
onChange: (newLayerState: DataLayersDisplayState) => void
cragsList: ActiveFeature[]
onSelectCrag: (crag: ActiveFeature) => void
}

/**
* Toolbar for filtering/toggling data layers
*/
export const MapToolbar: React.FC<MapToolbarProps> = ({ onChange, layerState }) => {
export const MapToolbar: React.FC<MapToolbarProps> = ({ layerState, onChange, cragsList, onSelectCrag }) => {
const { areaBoundaries, crags } = layerState
const [search, setSearch] = useState('')
const [suggestions, setSuggestions] = useState<ActiveFeature[]>([])

const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setSearch(value)
if (value.length === 0) {
setSuggestions([])
return
}

const filtered = cragsList.filter(c =>
c.data.areaName.toLowerCase().includes(value.toLowerCase())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use "crag" rather than "c" for var name

)
setSuggestions(filtered)
}

const handleSelect = (crag: ActiveFeature) => {
onSelectCrag(crag)
setSearch('')
setSuggestions([])
}

return (
<div className='absolute top-20 md:top-6 left-0 w-screen flex flex-col items-center justify-center'>
<ul className='p-2.5 flex items-center gap-4 bg-base-200 rounded-box shadow-md border'>
<div className='absolute top-6 left-1/2 transform -translate-x-1/2 flex flex-col items-center z-50'>
<ul className='p-2.5 flex items-center gap-4 bg-base-200 rounded-box shadow-md border mb-2'>
<Checkbox
value={crags}
label='Crags/Boulders'
Expand All @@ -24,6 +47,28 @@ export const MapToolbar: React.FC<MapToolbarProps> = ({ onChange, layerState })
label='Boundaries'
onChange={() => onChange({ ...layerState, areaBoundaries: !areaBoundaries })}
/>
<div className='relative w-64'>
<input
type="text"
value={search}
onChange={handleSearchChange}
placeholder="Search crags..."
className='input input-bordered w-full'
/>
{suggestions.length > 0 && (
<ul className='absolute top-full left-0 w-full bg-white shadow-lg rounded-md max-h-48 overflow-y-auto z-50'>
{suggestions.map(c => (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use "suggestion"rather than "c" for var name

<li
key={c.data.id}
onClick={() => handleSelect(c)}
className='cursor-pointer p-2 hover:bg-gray-200'
>
{c.data.areaName}
</li>
))}
</ul>
)}
</div>
Comment on lines +50 to +71
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation. This div has extra leading spaces compared to surrounding elements. It should align with the Checkbox components at the same nesting level.

Suggested change
<div className='relative w-64'>
<input
type="text"
value={search}
onChange={handleSearchChange}
placeholder="Search crags..."
className='input input-bordered w-full'
/>
{suggestions.length > 0 && (
<ul className='absolute top-full left-0 w-full bg-white shadow-lg rounded-md max-h-48 overflow-y-auto z-50'>
{suggestions.map(c => (
<li
key={c.data.id}
onClick={() => handleSelect(c)}
className='cursor-pointer p-2 hover:bg-gray-200'
>
{c.data.areaName}
</li>
))}
</ul>
)}
</div>
<div className='relative w-64'>
<input
type="text"
value={search}
onChange={handleSearchChange}
placeholder="Search crags..."
className='input input-bordered w-full'
/>
{suggestions.length > 0 && (
<ul className='absolute top-full left-0 w-full bg-white shadow-lg rounded-md max-h-48 overflow-y-auto z-50'>
{suggestions.map(c => (
<li
key={c.data.id}
onClick={() => handleSelect(c)}
className='cursor-pointer p-2 hover:bg-gray-200'
>
{c.data.areaName}
</li>
))}
</ul>
)}
</div>

Copilot uses AI. Check for mistakes.
</ul>
</div>
)
Expand Down