Extract components
This commit is contained in:
parent
a48eb945e0
commit
fe46cb3379
30 changed files with 4075 additions and 2610 deletions
225
frontend/src/components/map/map-page/DesktopMapPage.tsx
Normal file
225
frontend/src/components/map/map-page/DesktopMapPage.tsx
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
import { Suspense, type MutableRefObject, type ReactNode } from 'react';
|
||||
|
||||
import type { FeatureFilters, FeatureMeta, POI, PostcodeGeometry, ViewState } from '../../../types';
|
||||
import type { useMapData } from '../../../hooks/useMapData';
|
||||
import type { useTutorial } from '../../../hooks/useTutorial';
|
||||
import type { TravelTimeEntry } from '../../../hooks/useTravelTime';
|
||||
import type { getTutorialStyles } from '../../../lib/tutorial-styles';
|
||||
import type { SearchedLocation } from '../LocationSearch';
|
||||
import { MapPinIcon } from '../../ui/icons/MapPinIcon';
|
||||
import type { MapFlyTo, PaneResizeHandlers } from './types';
|
||||
import { MapFallback, PaneFallback } from './Fallbacks';
|
||||
import { LoadingOverlay } from './LoadingOverlay';
|
||||
import { Joyride, Map, MapPageSelectionPane } from './lazyComponents';
|
||||
|
||||
type MapData = ReturnType<typeof useMapData>;
|
||||
type Tutorial = ReturnType<typeof useTutorial>;
|
||||
type TutorialTheme = ReturnType<typeof getTutorialStyles>;
|
||||
type RightPaneTab = 'properties' | 'area';
|
||||
|
||||
interface DesktopMapPageProps {
|
||||
initialLoading: boolean;
|
||||
tutorial: Tutorial;
|
||||
tutorialTheme: TutorialTheme;
|
||||
leftPaneWidth: number;
|
||||
leftPaneHandlers: PaneResizeHandlers;
|
||||
filtersPane: ReactNode;
|
||||
mapData: MapData;
|
||||
pois: POI[];
|
||||
mapViewFeature: string | null;
|
||||
filterRange: [number, number] | null;
|
||||
viewSource: 'drag' | 'eye' | null;
|
||||
onCancelPin: () => void;
|
||||
features: FeatureMeta[];
|
||||
selectedHexagonId: string | null;
|
||||
hoveredHexagonId: string | null;
|
||||
onHexagonClick: (id: string, isPostcode?: boolean, geometry?: PostcodeGeometry) => void;
|
||||
onHexagonHover: (h3: string | null, x?: number, y?: number) => void;
|
||||
initialViewState: ViewState;
|
||||
flyToRef: MutableRefObject<MapFlyTo | null>;
|
||||
theme: 'light' | 'dark';
|
||||
filters: FeatureFilters;
|
||||
selectedPostcodeGeometry: PostcodeGeometry | null;
|
||||
onLocationSearched: (location: SearchedLocation | null) => void;
|
||||
onCurrentLocationFound: (lat: number, lng: number) => void;
|
||||
currentLocation: { lat: number; lng: number } | null;
|
||||
travelTimeEntries: TravelTimeEntry[];
|
||||
densityLabel: string;
|
||||
totalCount?: number;
|
||||
poiPaneOpen: boolean;
|
||||
onTogglePoiPane: () => void;
|
||||
poiPane: ReactNode;
|
||||
showSelectionPane: boolean;
|
||||
rightPaneWidth: number;
|
||||
rightPaneHandlers: PaneResizeHandlers;
|
||||
rightPaneTab: RightPaneTab;
|
||||
onAreaTabClick: () => void;
|
||||
onPropertiesTabClick: () => void;
|
||||
onCloseSelection: () => void;
|
||||
renderAreaPane: () => ReactNode;
|
||||
renderPropertiesPane: () => ReactNode;
|
||||
toasts: ReactNode;
|
||||
upgradeModal: ReactNode;
|
||||
}
|
||||
|
||||
export function DesktopMapPage({
|
||||
initialLoading,
|
||||
tutorial,
|
||||
tutorialTheme,
|
||||
leftPaneWidth,
|
||||
leftPaneHandlers,
|
||||
filtersPane,
|
||||
mapData,
|
||||
pois,
|
||||
mapViewFeature,
|
||||
filterRange,
|
||||
viewSource,
|
||||
onCancelPin,
|
||||
features,
|
||||
selectedHexagonId,
|
||||
hoveredHexagonId,
|
||||
onHexagonClick,
|
||||
onHexagonHover,
|
||||
initialViewState,
|
||||
flyToRef,
|
||||
theme,
|
||||
filters,
|
||||
selectedPostcodeGeometry,
|
||||
onLocationSearched,
|
||||
onCurrentLocationFound,
|
||||
currentLocation,
|
||||
travelTimeEntries,
|
||||
densityLabel,
|
||||
totalCount,
|
||||
poiPaneOpen,
|
||||
onTogglePoiPane,
|
||||
poiPane,
|
||||
showSelectionPane,
|
||||
rightPaneWidth,
|
||||
rightPaneHandlers,
|
||||
rightPaneTab,
|
||||
onAreaTabClick,
|
||||
onPropertiesTabClick,
|
||||
onCloseSelection,
|
||||
renderAreaPane,
|
||||
renderPropertiesPane,
|
||||
toasts,
|
||||
upgradeModal,
|
||||
}: DesktopMapPageProps) {
|
||||
return (
|
||||
<div className="flex-1 flex overflow-hidden relative">
|
||||
<LoadingOverlay show={initialLoading} />
|
||||
|
||||
{tutorial.run && (
|
||||
<Suspense fallback={null}>
|
||||
<Joyride
|
||||
steps={tutorial.steps}
|
||||
run={tutorial.run}
|
||||
continuous
|
||||
onEvent={tutorial.handleCallback}
|
||||
styles={tutorialTheme.styles}
|
||||
options={{
|
||||
...tutorialTheme.options,
|
||||
buttons: ['back', 'close', 'primary', 'skip'],
|
||||
showProgress: true,
|
||||
skipScroll: true,
|
||||
}}
|
||||
locale={{ last: 'Finish' }}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
<div
|
||||
data-tutorial="filters"
|
||||
className="flex bg-white dark:bg-navy-950 shadow-lg overflow-hidden"
|
||||
style={{ width: leftPaneWidth }}
|
||||
>
|
||||
<div className="flex-1 flex flex-col overflow-hidden">{filtersPane}</div>
|
||||
<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"
|
||||
{...leftPaneHandlers}
|
||||
>
|
||||
<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>
|
||||
|
||||
<div data-tutorial="map" className="flex-1 relative">
|
||||
{tutorial.run && (
|
||||
<div
|
||||
data-tutorial="map-anchor"
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute left-1/2 top-1/2 z-20 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-teal-500 shadow-lg ring-4 ring-teal-500/30 dark:border-navy-950"
|
||||
/>
|
||||
)}
|
||||
<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={onCancelPin}
|
||||
onResetPreviewScale={mapData.handleResetPreviewScale}
|
||||
canResetPreviewScale={mapData.canResetPreviewScale}
|
||||
features={features}
|
||||
selectedHexagonId={selectedHexagonId}
|
||||
hoveredHexagonId={hoveredHexagonId}
|
||||
onHexagonClick={onHexagonClick}
|
||||
onHexagonHover={onHexagonHover}
|
||||
initialViewState={initialViewState}
|
||||
flyToRef={flyToRef}
|
||||
theme={theme}
|
||||
filters={filters}
|
||||
selectedPostcodeGeometry={selectedPostcodeGeometry}
|
||||
onLocationSearched={onLocationSearched}
|
||||
onCurrentLocationFound={onCurrentLocationFound}
|
||||
currentLocation={currentLocation}
|
||||
bounds={mapData.bounds}
|
||||
travelTimeEntries={travelTimeEntries}
|
||||
densityLabel={densityLabel}
|
||||
totalCount={totalCount}
|
||||
/>
|
||||
</Suspense>
|
||||
<button
|
||||
data-tutorial="poi-button"
|
||||
onClick={onTogglePoiPane}
|
||||
className={`absolute bottom-4 right-4 z-10 px-3 py-2 rounded-lg shadow-lg bg-white dark:bg-warm-800 flex items-center gap-2 ${poiPaneOpen ? 'text-teal-600 dark:text-teal-400' : 'text-warm-500 dark:text-warm-400 hover:text-teal-600 dark:hover:text-teal-400'}`}
|
||||
>
|
||||
<MapPinIcon className="w-5 h-5" />
|
||||
<span className="text-sm font-medium">Points of interest</span>
|
||||
</button>
|
||||
{poiPaneOpen && (
|
||||
<div className="absolute bottom-14 right-4 z-10 flex h-[60vh] min-h-0 w-80 flex-col overflow-hidden rounded-lg border border-warm-200 bg-white shadow-xl dark:border-warm-700 dark:bg-warm-900">
|
||||
{poiPane}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showSelectionPane && (
|
||||
<Suspense fallback={<PaneFallback />}>
|
||||
<MapPageSelectionPane
|
||||
width={rightPaneWidth}
|
||||
resizeHandlers={rightPaneHandlers}
|
||||
tab={rightPaneTab}
|
||||
onAreaTabClick={onAreaTabClick}
|
||||
onPropertiesTabClick={onPropertiesTabClick}
|
||||
onClose={onCloseSelection}
|
||||
renderAreaPane={renderAreaPane}
|
||||
renderPropertiesPane={renderPropertiesPane}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
{toasts}
|
||||
{upgradeModal}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue