Small fixes
This commit is contained in:
parent
d56b5dedff
commit
d93beb9201
7 changed files with 95 additions and 42 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import MapPage, { type ExportState } from './components/map/MapPage';
|
||||
import PricingPage from './components/pricing/PricingPage';
|
||||
import HomePage from './components/home/HomePage';
|
||||
|
|
@ -67,9 +67,14 @@ function pathToPage(pathname: string): { page: Page; inviteCode?: string } | nul
|
|||
|
||||
export default function App() {
|
||||
const urlState = useMemo(() => parseUrlState(), []);
|
||||
const [mapUrlState, setMapUrlState] = useState(urlState);
|
||||
const dashboardSearchRef = useRef(
|
||||
window.location.pathname === '/dashboard' ? window.location.search : ''
|
||||
);
|
||||
const activePageRef = useRef<Page>('home');
|
||||
const initialViewState = useMemo(
|
||||
() => urlState.viewState || INITIAL_VIEW_STATE,
|
||||
[urlState.viewState]
|
||||
() => mapUrlState.viewState || INITIAL_VIEW_STATE,
|
||||
[mapUrlState.viewState]
|
||||
);
|
||||
|
||||
const isScreenshotMode = useMemo(() => {
|
||||
|
|
@ -179,17 +184,30 @@ export default function App() {
|
|||
|
||||
const navigateTo = useCallback(
|
||||
(page: Page, hash?: string, infoFeature?: string) => {
|
||||
// Save dashboard search params before navigating away
|
||||
if (activePageRef.current === 'dashboard') {
|
||||
dashboardSearchRef.current = window.location.search;
|
||||
}
|
||||
if (infoFeature) {
|
||||
window.history.replaceState({ ...window.history.state, infoFeature }, '');
|
||||
}
|
||||
const path = pageToPath(page, inviteCode ?? undefined);
|
||||
const url = hash ? `${path}#${hash}` : path;
|
||||
// Restore dashboard search params when navigating back
|
||||
const search = page === 'dashboard' ? dashboardSearchRef.current : '';
|
||||
const url = hash ? `${path}${search}#${hash}` : `${path}${search}`;
|
||||
window.history.pushState({ page }, '', url);
|
||||
if (page === 'dashboard') {
|
||||
setMapUrlState(parseUrlState());
|
||||
}
|
||||
setActivePage(page);
|
||||
},
|
||||
[inviteCode]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
activePageRef.current = activePage;
|
||||
}, [activePage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.history.state?.page) {
|
||||
window.history.replaceState(
|
||||
|
|
@ -199,17 +217,24 @@ export default function App() {
|
|||
);
|
||||
}
|
||||
const handlePopState = (e: PopStateEvent) => {
|
||||
let page: Page;
|
||||
if (e.state?.page) {
|
||||
setActivePage(e.state.page);
|
||||
page = e.state.page;
|
||||
setActivePage(page);
|
||||
if (e.state.infoFeature) {
|
||||
setPendingInfoFeature(e.state.infoFeature);
|
||||
}
|
||||
} else {
|
||||
// Fall back to deriving page from pathname
|
||||
const parsed = pathToPage(window.location.pathname);
|
||||
setActivePage(parsed?.page || 'home');
|
||||
page = parsed?.page || 'home';
|
||||
setActivePage(page);
|
||||
if (parsed?.inviteCode) setInviteCode(parsed.inviteCode);
|
||||
}
|
||||
// Re-parse URL state when returning to dashboard via back/forward
|
||||
if (page === 'dashboard') {
|
||||
setMapUrlState(parseUrlState());
|
||||
}
|
||||
};
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
|
|
@ -367,10 +392,10 @@ export default function App() {
|
|||
<MapPage
|
||||
features={features}
|
||||
poiCategoryGroups={poiCategoryGroups}
|
||||
initialFilters={urlState.filters || { 'Listing status': ['Historical sale'] }}
|
||||
initialFilters={mapUrlState.filters || { 'Listing status': ['Historical sale'] }}
|
||||
initialViewState={initialViewState}
|
||||
initialPOICategories={urlState.poiCategories || new Set()}
|
||||
initialTab={urlState.tab || 'area'}
|
||||
initialPOICategories={mapUrlState.poiCategories || new Set()}
|
||||
initialTab={mapUrlState.tab || 'area'}
|
||||
initialLoading={initialLoading}
|
||||
theme={theme}
|
||||
pendingInfoFeature={pendingInfoFeature}
|
||||
|
|
@ -378,8 +403,8 @@ export default function App() {
|
|||
onNavigateTo={navigateTo}
|
||||
onExportStateChange={setExportState}
|
||||
isMobile={isMobile}
|
||||
initialTravelTime={urlState.travelTime}
|
||||
initialPostcode={urlState.postcode}
|
||||
initialTravelTime={mapUrlState.travelTime}
|
||||
initialPostcode={mapUrlState.postcode}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
|
|
|
|||
|
|
@ -567,6 +567,7 @@ export default function MapPage({
|
|||
selectedCategories={selectedPOICategories}
|
||||
onCategoriesChange={setSelectedPOICategories}
|
||||
poiCount={pois.length}
|
||||
onClose={() => setPoiPaneOpen(false)}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import InfoPopup from '../ui/InfoPopup';
|
|||
import { SearchInput } from '../ui/SearchInput';
|
||||
import { PillToggle } from '../ui/PillToggle';
|
||||
import { PillGroup } from '../ui/PillGroup';
|
||||
import { InfoIcon, ChevronIcon } from '../ui/icons';
|
||||
import { InfoIcon, ChevronIcon, CloseIcon } from '../ui/icons';
|
||||
import { IconButton } from '../ui/IconButton';
|
||||
|
||||
interface POIPaneProps {
|
||||
|
|
@ -15,6 +15,7 @@ interface POIPaneProps {
|
|||
onCategoriesChange: (categories: Set<string>) => void;
|
||||
poiCount: number;
|
||||
onNavigateToSource?: (slug: string) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export default function POIPane({
|
||||
|
|
@ -23,6 +24,7 @@ export default function POIPane({
|
|||
onCategoriesChange,
|
||||
poiCount: _poiCount,
|
||||
onNavigateToSource,
|
||||
onClose,
|
||||
}: POIPaneProps) {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [isGroupExpanded, toggleCollapse] = useCollapsibleGroups();
|
||||
|
|
@ -96,7 +98,7 @@ export default function POIPane({
|
|||
<IconButton onClick={() => setShowInfo(true)} title="Data source info">
|
||||
<InfoIcon />
|
||||
</IconButton>
|
||||
<div className="flex gap-1 ml-auto">
|
||||
<div className="flex gap-1 ml-auto items-center">
|
||||
<button
|
||||
onClick={selectAll}
|
||||
className="px-2 py-0.5 text-xs rounded border border-warm-300 dark:border-warm-700 text-warm-600 dark:text-warm-400 hover:bg-warm-50 dark:hover:bg-warm-700"
|
||||
|
|
@ -109,6 +111,15 @@ export default function POIPane({
|
|||
>
|
||||
None
|
||||
</button>
|
||||
{onClose && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-1 p-0.5 text-warm-400 hover:text-warm-700 dark:hover:text-warm-300"
|
||||
title="Close"
|
||||
>
|
||||
<CloseIcon className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -316,10 +316,12 @@ export function useDeckLayers({
|
|||
number,
|
||||
];
|
||||
}
|
||||
const ttMin = (d[`min_${vf}`] as number) ?? ttVal;
|
||||
const ttMax = (d[`max_${vf}`] as number) ?? ttVal;
|
||||
return getFeatureFillColor(
|
||||
ttVal as number,
|
||||
ttVal as number,
|
||||
ttVal as number,
|
||||
ttMin as number,
|
||||
ttMax as number,
|
||||
clr,
|
||||
fr,
|
||||
0,
|
||||
|
|
@ -417,10 +419,12 @@ export function useDeckLayers({
|
|||
number,
|
||||
];
|
||||
}
|
||||
const ttMin = (d[`min_${vf}`] as number) ?? ttVal;
|
||||
const ttMax = (d[`max_${vf}`] as number) ?? ttVal;
|
||||
return getFeatureFillColor(
|
||||
ttVal as number,
|
||||
ttVal as number,
|
||||
ttVal as number,
|
||||
ttMin as number,
|
||||
ttMax as number,
|
||||
clr,
|
||||
fr,
|
||||
0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue