Codex changes

This commit is contained in:
Andras Schmelczer 2026-05-04 16:19:09 +01:00
parent 0bae902e08
commit d4dde21ad2
46 changed files with 4953 additions and 966 deletions

View file

@ -1,5 +1,6 @@
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { cellToLatLng } from 'h3-js';
import type {
FeatureMeta,
FeatureFilters,
@ -17,7 +18,7 @@ import { PropertiesPane } from './PropertiesPane';
import AreaPane from './AreaPane';
import MobileDrawer from './MobileDrawer';
import MapLegend from './MapLegend';
import { TabButton } from '../ui/TabButton';
import { MapPageSelectionPane } from './MapPageSelectionPane';
import { useMapData } from '../../hooks/useMapData';
import { usePOIData } from '../../hooks/usePOIData';
import { useFilters } from '../../hooks/useFilters';
@ -42,7 +43,6 @@ import { trackEvent } from '../../lib/analytics';
import { INITIAL_VIEW_STATE } from '../../lib/consts';
import { useLicense } from '../../hooks/useLicense';
import UpgradeModal from '../ui/UpgradeModal';
import { CloseIcon } from '../ui/icons/CloseIcon';
import { SpinnerIcon } from '../ui/icons/SpinnerIcon';
import { MapPinIcon } from '../ui/icons/MapPinIcon';
import { BookmarkIcon } from '../ui/icons/BookmarkIcon';
@ -125,6 +125,7 @@ export default function MapPage({
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [poiPaneOpen, setPoiPaneOpen] = useState(false);
const [currentLocation, setCurrentLocation] = useState<{ lat: number; lng: number } | null>(null);
const [showBookmarkToast, setShowBookmarkToast] = useState(false);
const bookmarkToastDismissed = useRef(localStorage.getItem('bookmark_toast_dismissed') === '1');
@ -249,7 +250,14 @@ export default function MapPage({
}
}
},
[fetchAiFilters, handleSetFilters, handleSetEntries, activeEntries, filters, mapData.currentView?.zoom]
[
fetchAiFilters,
handleSetFilters,
handleSetEntries,
activeEntries,
filters,
mapData.currentView?.zoom,
]
);
const handleClearAll = useCallback(() => {
@ -304,6 +312,7 @@ export default function MapPage({
loadingProperties,
areaStats,
loadingAreaStats,
unfilteredAreaCount,
hoveredHexagon,
rightPaneTab,
setRightPaneTab,
@ -315,25 +324,38 @@ export default function MapPage({
handleCloseSelection,
selectedPostcodeGeometry,
handleLocationSearch,
handleCurrentLocationSearch,
} = useHexagonSelection({
filters,
features,
resolution: mapData.resolution,
usePostcodeView: mapData.usePostcodeView,
journeyDest,
});
const handleLocationSearchResult = useCallback(
(result: SearchedLocation | null) => {
if (result) {
setCurrentLocation(null);
handleLocationSearch(result.postcode, result.geometry, result.latitude, result.longitude);
if (isMobile) setMobileDrawerOpen(true);
} else {
setCurrentLocation(null);
handleCloseSelection();
}
},
[handleLocationSearch, handleCloseSelection, isMobile]
);
const handleCurrentLocationFound = useCallback(
(lat: number, lng: number) => {
setCurrentLocation({ lat, lng });
handleCurrentLocationSearch(lat, lng);
if (isMobile) setMobileDrawerOpen(true);
},
[handleCurrentLocationSearch, isMobile]
);
const handleZoomToFreeZone = useCallback(() => {
mapFlyToRef.current?.(
INITIAL_VIEW_STATE.latitude,
@ -428,20 +450,19 @@ export default function MapPage({
const [lon, lat] = postcodeFeature.properties.centroid;
return { lat, lon, resolution: mapData.resolution, postcode: hexId, isPostcode: true };
} else {
// For hexagons, get lat/lon from hexagon data; central postcode comes from stats
const hex = hexId ? mapData.data.find((d) => d.h3 === hexId) : null;
if (!hex || typeof hex.lat !== 'number' || typeof hex.lon !== 'number') return null;
if (!hexId) return null;
const [lat, lon] = cellToLatLng(hexId);
return {
lat: hex.lat as number,
lon: hex.lon as number,
resolution: mapData.resolution,
lat,
lon,
resolution: selectedHexagon?.resolution ?? mapData.resolution,
postcode: areaStats?.central_postcode,
};
}
}, [
selectedHexagon?.id,
selectedHexagon?.resolution,
selectedHexagon?.type,
mapData.data,
mapData.postcodeData,
mapData.resolution,
areaStats?.central_postcode,
@ -487,6 +508,7 @@ export default function MapPage({
}, [mapData.licenseRequired]);
const densityLabel = t('mapLegend.historicalMatches');
const hasActiveFilters = Object.keys(filters).length > 0 || activeEntries.length > 0;
const mobileLegendMeta = useMemo(
() => (viewFeature ? features.find((f) => f.name === viewFeature) || null : null),
@ -607,8 +629,10 @@ export default function MapPage({
: null
}
onViewProperties={handleViewPropertiesFromArea}
onClearFilters={hasActiveFilters ? handleClearAll : undefined}
hexagonLocation={hexagonLocation}
filters={filters}
unfilteredCount={unfilteredAreaCount}
travelTimeEntries={activeEntries}
isGroupExpanded={isAreaGroupExpanded}
onToggleGroup={toggleAreaGroup}
@ -695,10 +719,7 @@ export default function MapPage({
</div>
)}
<div
ref={mobileMapRef}
className="relative overflow-hidden"
>
<div ref={mobileMapRef} className="relative overflow-hidden">
<Map
data={mapData.data}
postcodeData={mapData.postcodeData}
@ -721,6 +742,8 @@ export default function MapPage({
filters={filters}
selectedPostcodeGeometry={selectedPostcodeGeometry}
onLocationSearched={handleLocationSearchResult}
onCurrentLocationFound={handleCurrentLocationFound}
currentLocation={currentLocation}
bounds={mapData.bounds}
hideLegend
travelTimeEntries={entries}
@ -907,10 +930,12 @@ export default function MapPage({
filters={filters}
selectedPostcodeGeometry={selectedPostcodeGeometry}
onLocationSearched={handleLocationSearchResult}
onCurrentLocationFound={handleCurrentLocationFound}
currentLocation={currentLocation}
bounds={mapData.bounds}
travelTimeEntries={entries}
densityLabel={densityLabel}
totalCount={filterCounts.total || undefined}
totalCount={hasActiveFilters ? filterCounts.total : undefined}
/>
{mapData.loading && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
@ -940,47 +965,16 @@ export default function MapPage({
</div>
{selectedHexagon && (
<div
data-tutorial="right-pane"
className="flex bg-white dark:bg-navy-950 shadow-lg z-10"
style={{ width: rightPaneWidth }}
>
<div
className="w-3 cursor-col-resize flex items-center justify-center group bg-warm-100 dark:bg-navy-800 hover:bg-warm-200 dark:hover:bg-navy-700 border-x border-warm-200 dark:border-navy-700"
{...rightPaneHandlers}
>
<div className="flex flex-col gap-1.5">
<div className="w-1 h-1 rounded-full bg-warm-300 dark:bg-navy-600 group-hover:bg-warm-400 dark:group-hover:bg-navy-500" />
<div className="w-1 h-1 rounded-full bg-warm-300 dark:bg-navy-600 group-hover:bg-warm-400 dark:group-hover:bg-navy-500" />
<div className="w-1 h-1 rounded-full bg-warm-300 dark:bg-navy-600 group-hover:bg-warm-400 dark:group-hover:bg-navy-500" />
</div>
</div>
<div className="flex-1 flex flex-col overflow-hidden">
<div className="flex border-b border-warm-200 dark:border-navy-700 text-sm">
<TabButton
label="Area"
isActive={rightPaneTab === 'area'}
onClick={() => setRightPaneTab('area')}
/>
<TabButton
label="Properties"
isActive={rightPaneTab === 'properties'}
onClick={handlePropertiesTabClick}
/>
<button
onClick={handleCloseSelection}
className="px-2 flex items-center text-warm-400 hover:text-warm-700 dark:hover:text-warm-300"
title="Close pane"
>
<CloseIcon className="w-4 h-4" />
</button>
</div>
<div className="flex-1 overflow-hidden">
{rightPaneTab === 'properties' ? renderPropertiesPane() : renderAreaPane()}
</div>
</div>
</div>
<MapPageSelectionPane
width={rightPaneWidth}
resizeHandlers={rightPaneHandlers}
tab={rightPaneTab}
onAreaTabClick={() => setRightPaneTab('area')}
onPropertiesTabClick={handlePropertiesTabClick}
onClose={handleCloseSelection}
renderAreaPane={renderAreaPane}
renderPropertiesPane={renderPropertiesPane}
/>
)}
{bookmarkToast}