seems fine
This commit is contained in:
parent
48983e3b4b
commit
7a1696541f
37 changed files with 4999 additions and 1242 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import { lazy, Suspense, useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cellToLatLng } from 'h3-js';
|
||||
import type {
|
||||
|
|
@ -11,15 +11,8 @@ import type {
|
|||
} from '../../types';
|
||||
import type { SearchedLocation } from './LocationSearch';
|
||||
import type { Page } from '../ui/Header';
|
||||
import Map from './Map';
|
||||
import Filters from './Filters';
|
||||
import POIPane from './POIPane';
|
||||
import { PropertiesPane } from './PropertiesPane';
|
||||
import AreaPane from './AreaPane';
|
||||
import MobileDrawer from './MobileDrawer';
|
||||
import MobileBottomSheet from './MobileBottomSheet';
|
||||
import MapLegend from './MapLegend';
|
||||
import { MapPageSelectionPane } from './MapPageSelectionPane';
|
||||
import { useMapData } from '../../hooks/useMapData';
|
||||
import { usePOIData } from '../../hooks/usePOIData';
|
||||
import { useFilters } from '../../hooks/useFilters';
|
||||
|
|
@ -30,7 +23,6 @@ import { useAiFilters } from '../../hooks/useAiFilters';
|
|||
import { useUrlSync } from '../../hooks/useUrlSync';
|
||||
import { useTutorial } from '../../hooks/useTutorial';
|
||||
import { getTutorialStyles } from '../../lib/tutorial-styles';
|
||||
import Joyride from 'react-joyride';
|
||||
import {
|
||||
useTravelTime,
|
||||
useTranslatedModes,
|
||||
|
|
@ -44,11 +36,40 @@ import { trackEvent } from '../../lib/analytics';
|
|||
import { INITIAL_VIEW_STATE } from '../../lib/consts';
|
||||
import { getSchoolBackendFeatureName } from '../../lib/school-filter';
|
||||
import { useLicense } from '../../hooks/useLicense';
|
||||
import UpgradeModal from '../ui/UpgradeModal';
|
||||
import { SpinnerIcon } from '../ui/icons/SpinnerIcon';
|
||||
import { MapPinIcon } from '../ui/icons/MapPinIcon';
|
||||
import { BookmarkIcon } from '../ui/icons/BookmarkIcon';
|
||||
|
||||
const Map = lazy(() => import('./Map'));
|
||||
const Filters = lazy(() => import('./Filters'));
|
||||
const POIPane = lazy(() => import('./POIPane'));
|
||||
const AreaPane = lazy(() => import('./AreaPane'));
|
||||
const PropertiesPane = lazy(() =>
|
||||
import('./PropertiesPane').then((module) => ({ default: module.PropertiesPane }))
|
||||
);
|
||||
const MobileDrawer = lazy(() => import('./MobileDrawer'));
|
||||
const MapPageSelectionPane = lazy(() =>
|
||||
import('./MapPageSelectionPane').then((module) => ({ default: module.MapPageSelectionPane }))
|
||||
);
|
||||
const UpgradeModal = lazy(() => import('../ui/UpgradeModal'));
|
||||
const Joyride = lazy(() => import('react-joyride'));
|
||||
|
||||
function MapFallback() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-warm-100 dark:bg-navy-950">
|
||||
<SpinnerIcon className="h-8 w-8 animate-spin text-teal-600 dark:text-teal-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PaneFallback() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-white dark:bg-navy-950">
|
||||
<SpinnerIcon className="h-6 w-6 animate-spin text-teal-600 dark:text-teal-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ExportState {
|
||||
onExport: () => void;
|
||||
exporting: boolean;
|
||||
|
|
@ -193,6 +214,7 @@ export default function MapPage({
|
|||
features,
|
||||
viewFeature,
|
||||
activeFeature,
|
||||
pinnedFeature,
|
||||
travelTimeEntries: entries,
|
||||
shareCode,
|
||||
});
|
||||
|
|
@ -335,8 +357,19 @@ export default function MapPage({
|
|||
const handleLocationSearchResult = useCallback(
|
||||
(result: SearchedLocation | null) => {
|
||||
if (result) {
|
||||
setCurrentLocation(null);
|
||||
handleLocationSearch(result.postcode, result.geometry, result.latitude, result.longitude);
|
||||
if (result.markerLatitude != null && result.markerLongitude != null) {
|
||||
setCurrentLocation({ lat: result.markerLatitude, lng: result.markerLongitude });
|
||||
} else {
|
||||
setCurrentLocation(null);
|
||||
}
|
||||
handleLocationSearch(
|
||||
result.postcode,
|
||||
result.geometry,
|
||||
result.latitude,
|
||||
result.longitude,
|
||||
result.openProperties,
|
||||
result.focusAddress
|
||||
);
|
||||
if (isMobile) setMobileDrawerOpen(true);
|
||||
} else {
|
||||
setCurrentLocation(null);
|
||||
|
|
@ -604,121 +637,134 @@ export default function MapPage({
|
|||
if (screenshotMode) {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<Map
|
||||
data={mapData.data}
|
||||
postcodeData={mapData.postcodeData}
|
||||
usePostcodeView={mapData.usePostcodeView}
|
||||
pois={[]}
|
||||
onViewChange={mapData.handleViewChange}
|
||||
viewFeature={mapViewFeature}
|
||||
colorRange={mapData.colorRange}
|
||||
filterRange={filterRange}
|
||||
viewSource={viewSource}
|
||||
onCancelPin={() => {}}
|
||||
features={features}
|
||||
selectedHexagonId={null}
|
||||
hoveredHexagonId={null}
|
||||
onHexagonClick={() => {}}
|
||||
onHexagonHover={() => {}}
|
||||
initialViewState={initialViewState}
|
||||
theme={theme}
|
||||
screenshotMode
|
||||
ogMode={ogMode}
|
||||
bounds={mapData.bounds}
|
||||
travelTimeEntries={entries}
|
||||
/>
|
||||
<Suspense fallback={<MapFallback />}>
|
||||
<Map
|
||||
data={mapData.data}
|
||||
postcodeData={mapData.postcodeData}
|
||||
usePostcodeView={mapData.usePostcodeView}
|
||||
pois={[]}
|
||||
onViewChange={mapData.handleViewChange}
|
||||
viewFeature={mapViewFeature}
|
||||
colorRange={mapData.colorRange}
|
||||
filterRange={filterRange}
|
||||
viewSource={viewSource}
|
||||
onCancelPin={() => {}}
|
||||
onResetPreviewScale={mapData.handleResetPreviewScale}
|
||||
canResetPreviewScale={mapData.canResetPreviewScale}
|
||||
features={features}
|
||||
selectedHexagonId={null}
|
||||
hoveredHexagonId={null}
|
||||
onHexagonClick={() => {}}
|
||||
onHexagonHover={() => {}}
|
||||
initialViewState={initialViewState}
|
||||
theme={theme}
|
||||
screenshotMode
|
||||
ogMode={ogMode}
|
||||
bounds={mapData.bounds}
|
||||
travelTimeEntries={entries}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderAreaPane = () => (
|
||||
<AreaPane
|
||||
stats={areaStats}
|
||||
globalFeatures={features}
|
||||
loading={loadingAreaStats}
|
||||
hexagonId={selectedHexagon?.id || null}
|
||||
isPostcode={selectedHexagon?.type === 'postcode'}
|
||||
postcodeData={
|
||||
selectedHexagon?.type === 'postcode'
|
||||
? mapData.postcodeData.find((f) => f.properties.postcode === selectedHexagon?.id) || null
|
||||
: null
|
||||
}
|
||||
onViewProperties={handleViewPropertiesFromArea}
|
||||
onClearFilters={hasActiveFilters ? handleClearAll : undefined}
|
||||
hexagonLocation={hexagonLocation}
|
||||
filters={filters}
|
||||
unfilteredCount={unfilteredAreaCount}
|
||||
travelTimeEntries={activeEntries}
|
||||
isGroupExpanded={isAreaGroupExpanded}
|
||||
onToggleGroup={toggleAreaGroup}
|
||||
/>
|
||||
<Suspense fallback={<PaneFallback />}>
|
||||
<AreaPane
|
||||
stats={areaStats}
|
||||
globalFeatures={features}
|
||||
loading={loadingAreaStats}
|
||||
hexagonId={selectedHexagon?.id || null}
|
||||
isPostcode={selectedHexagon?.type === 'postcode'}
|
||||
postcodeData={
|
||||
selectedHexagon?.type === 'postcode'
|
||||
? mapData.postcodeData.find((f) => f.properties.postcode === selectedHexagon?.id) ||
|
||||
null
|
||||
: null
|
||||
}
|
||||
onViewProperties={handleViewPropertiesFromArea}
|
||||
onClearFilters={hasActiveFilters ? handleClearAll : undefined}
|
||||
hexagonLocation={hexagonLocation}
|
||||
filters={filters}
|
||||
unfilteredCount={unfilteredAreaCount}
|
||||
travelTimeEntries={activeEntries}
|
||||
isGroupExpanded={isAreaGroupExpanded}
|
||||
onToggleGroup={toggleAreaGroup}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
const renderPropertiesPane = () => (
|
||||
<PropertiesPane
|
||||
properties={properties}
|
||||
total={propertiesTotal}
|
||||
loading={loadingProperties}
|
||||
hexagonId={selectedHexagon?.id || null}
|
||||
onLoadMore={handleLoadMoreProperties}
|
||||
onSaveProperty={onSaveProperty ? handleSavePropertyWithToast : undefined}
|
||||
onUnsaveProperty={onUnsaveProperty}
|
||||
isPropertySaved={isPropertySaved}
|
||||
getSavedPropertyId={getSavedPropertyId}
|
||||
/>
|
||||
<Suspense fallback={<PaneFallback />}>
|
||||
<PropertiesPane
|
||||
properties={properties}
|
||||
total={propertiesTotal}
|
||||
loading={loadingProperties}
|
||||
hexagonId={selectedHexagon?.id || null}
|
||||
onLoadMore={handleLoadMoreProperties}
|
||||
onSaveProperty={onSaveProperty ? handleSavePropertyWithToast : undefined}
|
||||
onUnsaveProperty={onUnsaveProperty}
|
||||
isPropertySaved={isPropertySaved}
|
||||
getSavedPropertyId={getSavedPropertyId}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
const renderPOIPane = () => (
|
||||
<POIPane
|
||||
groups={poiCategoryGroups}
|
||||
selectedCategories={selectedPOICategories}
|
||||
onCategoriesChange={setSelectedPOICategories}
|
||||
poiCount={pois.length}
|
||||
onClose={() => setPoiPaneOpen(false)}
|
||||
/>
|
||||
<Suspense fallback={<PaneFallback />}>
|
||||
<POIPane
|
||||
groups={poiCategoryGroups}
|
||||
selectedCategories={selectedPOICategories}
|
||||
onCategoriesChange={setSelectedPOICategories}
|
||||
poiCount={pois.length}
|
||||
onClose={() => setPoiPaneOpen(false)}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
const renderFilters = (options?: { destinationDropdownPortal?: boolean }) => (
|
||||
<Filters
|
||||
features={features}
|
||||
filters={filters}
|
||||
activeFeature={activeFeature}
|
||||
dragValue={dragValue}
|
||||
enabledFeatures={enabledFeatures}
|
||||
onAddFilter={handleAddFilter}
|
||||
onRemoveFilter={handleRemoveFilter}
|
||||
onFilterChange={handleFilterChange}
|
||||
onDragStart={handleDragStart}
|
||||
onDragChange={handleDragChange}
|
||||
onDragEnd={handleDragEnd}
|
||||
pinnedFeature={pinnedFeature}
|
||||
onTogglePin={handleTogglePin}
|
||||
openInfoFeature={pendingInfoFeature}
|
||||
onClearOpenInfoFeature={onClearPendingInfoFeature}
|
||||
travelTimeEntries={entries}
|
||||
onTravelTimeAddEntry={handleAddEntry}
|
||||
onTravelTimeRemoveEntry={handleTravelTimeRemoveEntry}
|
||||
onTravelTimeSetDestination={handleTravelTimeSetDestination}
|
||||
onTravelTimeRangeChange={handleTimeRangeChange}
|
||||
onTravelTimeDragEnd={handleTravelTimeDragEnd}
|
||||
onTravelTimeToggleBest={handleToggleBest}
|
||||
aiFilterLoading={aiFilterLoading}
|
||||
aiFilterError={aiFilterError}
|
||||
aiFilterErrorType={aiFilterErrorType}
|
||||
aiFilterNotes={aiFilterNotes}
|
||||
aiFilterSummary={aiFilterSummary}
|
||||
onAiFilterSubmit={handleAiFilterSubmit}
|
||||
isLoggedIn={!!user}
|
||||
onLoginRequired={onRegisterClick ?? (() => {})}
|
||||
isLicensed={user?.subscription === 'licensed'}
|
||||
onUpgradeClick={() => onNavigateTo('pricing')}
|
||||
onResetTutorial={tutorial.resetTutorial}
|
||||
filterImpacts={filterCounts.impacts}
|
||||
onClearAll={handleClearAll}
|
||||
onSaveSearch={onSaveSearch}
|
||||
savingSearch={savingSearch}
|
||||
destinationDropdownPortal={options?.destinationDropdownPortal}
|
||||
/>
|
||||
<Suspense fallback={<PaneFallback />}>
|
||||
<Filters
|
||||
features={features}
|
||||
filters={filters}
|
||||
activeFeature={activeFeature}
|
||||
dragValue={dragValue}
|
||||
enabledFeatures={enabledFeatures}
|
||||
onAddFilter={handleAddFilter}
|
||||
onRemoveFilter={handleRemoveFilter}
|
||||
onFilterChange={handleFilterChange}
|
||||
onDragStart={handleDragStart}
|
||||
onDragChange={handleDragChange}
|
||||
onDragEnd={handleDragEnd}
|
||||
pinnedFeature={pinnedFeature}
|
||||
onTogglePin={handleTogglePin}
|
||||
openInfoFeature={pendingInfoFeature}
|
||||
onClearOpenInfoFeature={onClearPendingInfoFeature}
|
||||
travelTimeEntries={entries}
|
||||
onTravelTimeAddEntry={handleAddEntry}
|
||||
onTravelTimeRemoveEntry={handleTravelTimeRemoveEntry}
|
||||
onTravelTimeSetDestination={handleTravelTimeSetDestination}
|
||||
onTravelTimeRangeChange={handleTimeRangeChange}
|
||||
onTravelTimeDragEnd={handleTravelTimeDragEnd}
|
||||
onTravelTimeToggleBest={handleToggleBest}
|
||||
aiFilterLoading={aiFilterLoading}
|
||||
aiFilterError={aiFilterError}
|
||||
aiFilterErrorType={aiFilterErrorType}
|
||||
aiFilterNotes={aiFilterNotes}
|
||||
aiFilterSummary={aiFilterSummary}
|
||||
onAiFilterSubmit={handleAiFilterSubmit}
|
||||
isLoggedIn={!!user}
|
||||
onLoginRequired={onRegisterClick ?? (() => {})}
|
||||
isLicensed={user?.subscription === 'licensed'}
|
||||
onUpgradeClick={() => onNavigateTo('pricing')}
|
||||
onResetTutorial={tutorial.resetTutorial}
|
||||
filterImpacts={filterCounts.impacts}
|
||||
onClearAll={handleClearAll}
|
||||
onSaveSearch={onSaveSearch}
|
||||
savingSearch={savingSearch}
|
||||
destinationDropdownPortal={options?.destinationDropdownPortal}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
const renderMobileLegend = () => {
|
||||
|
|
@ -734,6 +780,8 @@ export default function MapPage({
|
|||
range={mapData.colorRange}
|
||||
showCancel={viewSource === 'eye'}
|
||||
onCancel={handleCancelPin}
|
||||
onResetScale={viewSource === 'eye' ? mapData.handleResetPreviewScale : undefined}
|
||||
resetScaleDisabled={!mapData.canResetPreviewScale}
|
||||
mode="feature"
|
||||
theme={theme}
|
||||
inline
|
||||
|
|
@ -753,6 +801,8 @@ export default function MapPage({
|
|||
range={mapData.colorRange}
|
||||
showCancel={viewSource === 'eye'}
|
||||
onCancel={handleCancelPin}
|
||||
onResetScale={viewSource === 'eye' ? mapData.handleResetPreviewScale : undefined}
|
||||
resetScaleDisabled={!mapData.canResetPreviewScale}
|
||||
mode="feature"
|
||||
enumValues={mobileLegendMeta.type === 'enum' ? mobileLegendMeta.values : undefined}
|
||||
featureName={mobileLegendMeta.name}
|
||||
|
|
@ -794,34 +844,38 @@ export default function MapPage({
|
|||
)}
|
||||
|
||||
<div className="absolute inset-0">
|
||||
<Map
|
||||
data={mapData.data}
|
||||
postcodeData={mapData.postcodeData}
|
||||
usePostcodeView={mapData.usePostcodeView}
|
||||
pois={pois}
|
||||
onViewChange={mapData.handleViewChange}
|
||||
viewFeature={mapViewFeature}
|
||||
colorRange={mapData.colorRange}
|
||||
filterRange={filterRange}
|
||||
viewSource={viewSource}
|
||||
onCancelPin={handleCancelPin}
|
||||
features={features}
|
||||
selectedHexagonId={selectedHexagon?.id || null}
|
||||
hoveredHexagonId={hoveredHexagon}
|
||||
onHexagonClick={handleMobileHexagonClick}
|
||||
onHexagonHover={handleHexagonHover}
|
||||
initialViewState={initialViewState}
|
||||
flyToRef={mapFlyToRef}
|
||||
theme={theme}
|
||||
filters={filters}
|
||||
selectedPostcodeGeometry={selectedPostcodeGeometry}
|
||||
onLocationSearched={handleLocationSearchResult}
|
||||
onCurrentLocationFound={handleCurrentLocationFound}
|
||||
currentLocation={currentLocation}
|
||||
bounds={mapData.bounds}
|
||||
hideLegend
|
||||
travelTimeEntries={entries}
|
||||
/>
|
||||
<Suspense fallback={<MapFallback />}>
|
||||
<Map
|
||||
data={mapData.data}
|
||||
postcodeData={mapData.postcodeData}
|
||||
usePostcodeView={mapData.usePostcodeView}
|
||||
pois={pois}
|
||||
onViewChange={mapData.handleViewChange}
|
||||
viewFeature={mapViewFeature}
|
||||
colorRange={mapData.colorRange}
|
||||
filterRange={filterRange}
|
||||
viewSource={viewSource}
|
||||
onCancelPin={handleCancelPin}
|
||||
onResetPreviewScale={mapData.handleResetPreviewScale}
|
||||
canResetPreviewScale={mapData.canResetPreviewScale}
|
||||
features={features}
|
||||
selectedHexagonId={selectedHexagon?.id || null}
|
||||
hoveredHexagonId={hoveredHexagon}
|
||||
onHexagonClick={handleMobileHexagonClick}
|
||||
onHexagonHover={handleHexagonHover}
|
||||
initialViewState={initialViewState}
|
||||
flyToRef={mapFlyToRef}
|
||||
theme={theme}
|
||||
filters={filters}
|
||||
selectedPostcodeGeometry={selectedPostcodeGeometry}
|
||||
onLocationSearched={handleLocationSearchResult}
|
||||
onCurrentLocationFound={handleCurrentLocationFound}
|
||||
currentLocation={currentLocation}
|
||||
bounds={mapData.bounds}
|
||||
hideLegend
|
||||
travelTimeEntries={entries}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
{mapData.loading && (
|
||||
|
|
@ -849,40 +903,41 @@ export default function MapPage({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<MobileBottomSheet
|
||||
activeCount={Object.keys(filters).length + entries.length}
|
||||
legend={renderMobileLegend()}
|
||||
>
|
||||
<MobileBottomSheet legend={renderMobileLegend()}>
|
||||
{renderFilters({ destinationDropdownPortal: false })}
|
||||
</MobileBottomSheet>
|
||||
|
||||
{mobileDrawerOpen && selectedHexagon && (
|
||||
<MobileDrawer
|
||||
onClose={() => setMobileDrawerOpen(false)}
|
||||
renderArea={renderAreaPane}
|
||||
renderProperties={renderPropertiesPane}
|
||||
tab={rightPaneTab}
|
||||
onTabChange={(t) => {
|
||||
if (t === 'properties') {
|
||||
handlePropertiesTabClick();
|
||||
} else {
|
||||
setRightPaneTab(t);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Suspense fallback={<PaneFallback />}>
|
||||
<MobileDrawer
|
||||
onClose={() => setMobileDrawerOpen(false)}
|
||||
renderArea={renderAreaPane}
|
||||
renderProperties={renderPropertiesPane}
|
||||
tab={rightPaneTab}
|
||||
onTabChange={(t) => {
|
||||
if (t === 'properties') {
|
||||
handlePropertiesTabClick();
|
||||
} else {
|
||||
setRightPaneTab(t);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
{bookmarkToast}
|
||||
|
||||
{mapData.licenseRequired && (
|
||||
<UpgradeModal
|
||||
isLoggedIn={!!user}
|
||||
onLoginClick={onLoginClick ?? (() => {})}
|
||||
onRegisterClick={onRegisterClick ?? (() => {})}
|
||||
onStartCheckout={() => license.startCheckout()}
|
||||
onZoomToFreeZone={handleZoomToFreeZone}
|
||||
isShareReturn={!!shareReturnViewRef.current}
|
||||
/>
|
||||
<Suspense fallback={null}>
|
||||
<UpgradeModal
|
||||
isLoggedIn={!!user}
|
||||
onLoginClick={onLoginClick ?? (() => {})}
|
||||
onRegisterClick={onRegisterClick ?? (() => {})}
|
||||
onStartCheckout={() => license.startCheckout()}
|
||||
onZoomToFreeZone={handleZoomToFreeZone}
|
||||
isShareReturn={!!shareReturnViewRef.current}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -901,17 +956,21 @@ export default function MapPage({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<Joyride
|
||||
steps={tutorial.steps}
|
||||
run={tutorial.run}
|
||||
continuous
|
||||
showProgress
|
||||
showSkipButton
|
||||
callback={tutorial.handleCallback}
|
||||
styles={getTutorialStyles(theme)}
|
||||
disableScrolling
|
||||
locale={{ last: 'Finish' }}
|
||||
/>
|
||||
{tutorial.run && (
|
||||
<Suspense fallback={null}>
|
||||
<Joyride
|
||||
steps={tutorial.steps}
|
||||
run={tutorial.run}
|
||||
continuous
|
||||
showProgress
|
||||
showSkipButton
|
||||
callback={tutorial.handleCallback}
|
||||
styles={getTutorialStyles(theme)}
|
||||
disableScrolling
|
||||
locale={{ last: 'Finish' }}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
<div
|
||||
data-tutorial="filters"
|
||||
|
|
@ -932,35 +991,39 @@ export default function MapPage({
|
|||
</div>
|
||||
|
||||
<div data-tutorial="map" className="flex-1 relative">
|
||||
<Map
|
||||
data={mapData.data}
|
||||
postcodeData={mapData.postcodeData}
|
||||
usePostcodeView={mapData.usePostcodeView}
|
||||
pois={pois}
|
||||
onViewChange={mapData.handleViewChange}
|
||||
viewFeature={mapViewFeature}
|
||||
colorRange={mapData.colorRange}
|
||||
filterRange={filterRange}
|
||||
viewSource={viewSource}
|
||||
onCancelPin={handleCancelPin}
|
||||
features={features}
|
||||
selectedHexagonId={selectedHexagon?.id || null}
|
||||
hoveredHexagonId={hoveredHexagon}
|
||||
onHexagonClick={handleHexagonClick}
|
||||
onHexagonHover={handleHexagonHover}
|
||||
initialViewState={initialViewState}
|
||||
flyToRef={mapFlyToRef}
|
||||
theme={theme}
|
||||
filters={filters}
|
||||
selectedPostcodeGeometry={selectedPostcodeGeometry}
|
||||
onLocationSearched={handleLocationSearchResult}
|
||||
onCurrentLocationFound={handleCurrentLocationFound}
|
||||
currentLocation={currentLocation}
|
||||
bounds={mapData.bounds}
|
||||
travelTimeEntries={entries}
|
||||
densityLabel={densityLabel}
|
||||
totalCount={hasActiveFilters ? filterCounts.total : undefined}
|
||||
/>
|
||||
<Suspense fallback={<MapFallback />}>
|
||||
<Map
|
||||
data={mapData.data}
|
||||
postcodeData={mapData.postcodeData}
|
||||
usePostcodeView={mapData.usePostcodeView}
|
||||
pois={pois}
|
||||
onViewChange={mapData.handleViewChange}
|
||||
viewFeature={mapViewFeature}
|
||||
colorRange={mapData.colorRange}
|
||||
filterRange={filterRange}
|
||||
viewSource={viewSource}
|
||||
onCancelPin={handleCancelPin}
|
||||
onResetPreviewScale={mapData.handleResetPreviewScale}
|
||||
canResetPreviewScale={mapData.canResetPreviewScale}
|
||||
features={features}
|
||||
selectedHexagonId={selectedHexagon?.id || null}
|
||||
hoveredHexagonId={hoveredHexagon}
|
||||
onHexagonClick={handleHexagonClick}
|
||||
onHexagonHover={handleHexagonHover}
|
||||
initialViewState={initialViewState}
|
||||
flyToRef={mapFlyToRef}
|
||||
theme={theme}
|
||||
filters={filters}
|
||||
selectedPostcodeGeometry={selectedPostcodeGeometry}
|
||||
onLocationSearched={handleLocationSearchResult}
|
||||
onCurrentLocationFound={handleCurrentLocationFound}
|
||||
currentLocation={currentLocation}
|
||||
bounds={mapData.bounds}
|
||||
travelTimeEntries={entries}
|
||||
densityLabel={densityLabel}
|
||||
totalCount={hasActiveFilters ? filterCounts.total : undefined}
|
||||
/>
|
||||
</Suspense>
|
||||
{mapData.loading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<div className="flex items-center gap-2 bg-white/90 dark:bg-warm-800/90 px-4 py-2.5 rounded-lg shadow-lg pointer-events-auto">
|
||||
|
|
@ -989,29 +1052,33 @@ export default function MapPage({
|
|||
</div>
|
||||
|
||||
{selectedHexagon && (
|
||||
<MapPageSelectionPane
|
||||
width={rightPaneWidth}
|
||||
resizeHandlers={rightPaneHandlers}
|
||||
tab={rightPaneTab}
|
||||
onAreaTabClick={() => setRightPaneTab('area')}
|
||||
onPropertiesTabClick={handlePropertiesTabClick}
|
||||
onClose={handleCloseSelection}
|
||||
renderAreaPane={renderAreaPane}
|
||||
renderPropertiesPane={renderPropertiesPane}
|
||||
/>
|
||||
<Suspense fallback={<PaneFallback />}>
|
||||
<MapPageSelectionPane
|
||||
width={rightPaneWidth}
|
||||
resizeHandlers={rightPaneHandlers}
|
||||
tab={rightPaneTab}
|
||||
onAreaTabClick={() => setRightPaneTab('area')}
|
||||
onPropertiesTabClick={handlePropertiesTabClick}
|
||||
onClose={handleCloseSelection}
|
||||
renderAreaPane={renderAreaPane}
|
||||
renderPropertiesPane={renderPropertiesPane}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
{bookmarkToast}
|
||||
|
||||
{mapData.licenseRequired && (
|
||||
<UpgradeModal
|
||||
isLoggedIn={!!user}
|
||||
onLoginClick={onLoginClick ?? (() => {})}
|
||||
onRegisterClick={onRegisterClick ?? (() => {})}
|
||||
onStartCheckout={() => license.startCheckout()}
|
||||
onZoomToFreeZone={handleZoomToFreeZone}
|
||||
isShareReturn={!!shareReturnViewRef.current}
|
||||
/>
|
||||
<Suspense fallback={null}>
|
||||
<UpgradeModal
|
||||
isLoggedIn={!!user}
|
||||
onLoginClick={onLoginClick ?? (() => {})}
|
||||
onRegisterClick={onRegisterClick ?? (() => {})}
|
||||
onStartCheckout={() => license.startCheckout()}
|
||||
onZoomToFreeZone={handleZoomToFreeZone}
|
||||
isShareReturn={!!shareReturnViewRef.current}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue