Google Maps Bug

This commit is contained in:
Eric Gullickson
2025-11-08 12:17:29 -06:00
parent efbe9ba3c0
commit bb4a356b9e
39 changed files with 1175 additions and 449 deletions

View File

@@ -15,12 +15,13 @@ import {
CircularProgress,
Typography
} from '@mui/material';
import { Station, StationSearchRequest } from '../types/stations.types';
import { OctanePreference, SavedStation, Station, StationSearchRequest } from '../types/stations.types';
import {
useStationsSearch,
useSavedStations,
useSaveStation,
useDeleteStation
useDeleteStation,
useUpdateSavedStation
} from '../hooks';
import {
StationMap,
@@ -29,6 +30,7 @@ import {
StationsSearchForm,
GoogleMapsErrorBoundary
} from '../components';
import { octanePreferenceToFlags, resolveSavedStationPlaceId } from '../utils/savedStations';
interface TabPanelProps {
children?: React.ReactNode;
@@ -119,12 +121,30 @@ export const StationsPage: React.FC = () => {
const { mutate: saveStation } = useSaveStation();
const { mutate: deleteStation } = useDeleteStation();
const { mutate: updateSavedStation } = useUpdateSavedStation();
const [octaneUpdatingId, setOctaneUpdatingId] = useState<string | null>(null);
// Create set of saved place IDs for quick lookup
const savedPlaceIds = useMemo(
() => new Set(savedStations.map((s) => s.placeId)),
[savedStations]
);
const { savedStationsMap, savedPlaceIds } = useMemo(() => {
const map = new Map<string, SavedStation>();
savedStations.forEach((station) => {
const placeId = resolveSavedStationPlaceId(station);
if (!placeId) {
return;
}
const normalizedStation =
station.placeId === placeId ? station : { ...station, placeId };
map.set(placeId, normalizedStation);
});
return {
savedStationsMap: map,
savedPlaceIds: new Set(map.keys())
};
}, [savedStations]);
// Handle search
const handleSearch = (request: StationSearchRequest) => {
@@ -163,6 +183,23 @@ export const StationsPage: React.FC = () => {
deleteStation(placeId);
};
const handleOctanePreferenceChange = useCallback(
(placeId: string, preference: OctanePreference) => {
const flags = octanePreferenceToFlags(preference);
setOctaneUpdatingId(placeId);
updateSavedStation(
{ placeId, data: flags },
{
onSettled: () => {
setOctaneUpdatingId((current) => (current === placeId ? null : current));
}
}
);
},
[updateSavedStation]
);
// Handle station selection - wrapped in useCallback to prevent infinite renders
const handleSelectStation = useCallback((station: Station) => {
setMapCenter({
@@ -238,6 +275,7 @@ export const StationsPage: React.FC = () => {
<StationsList
stations={searchResults}
savedPlaceIds={savedPlaceIds}
savedStationsMap={savedStationsMap}
loading={isSearching}
error={searchError ? (searchError as any).message : null}
onSaveStation={handleSave}
@@ -252,90 +290,118 @@ export const StationsPage: React.FC = () => {
error={savedError ? (savedError as any).message : null}
onSelectStation={handleSelectStation}
onDeleteStation={handleDelete}
onOctanePreferenceChange={handleOctanePreferenceChange}
octaneUpdatingId={octaneUpdatingId}
/>
</TabPanel>
</Box>
);
}
// Desktop layout: side-by-side
// Desktop layout: top-row map + search, full-width results
return (
<Grid container spacing={2} sx={{ padding: 2, height: 'calc(100vh - 80px)' }}>
{/* Left: Map (60%) */}
<Grid item xs={12} md={6} sx={{ display: 'flex', flexDirection: 'column' }}>
<Paper sx={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
{isMapReady ? (
<GoogleMapsErrorBoundary>
<StationMap
key="desktop-station-map"
stations={searchResults}
savedPlaceIds={savedPlaceIds}
currentLocation={currentLocation}
center={mapCenter || undefined}
height="100%"
readyToRender={true}
/>
</GoogleMapsErrorBoundary>
) : (
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<CircularProgress />
</Box>
)}
</Paper>
</Grid>
{/* Right: Search + Tabs (40%) */}
<Grid item xs={12} md={6} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{/* Search Form */}
<Paper>
<StationsSearchForm onSearch={handleSearch} isSearching={isSearching} />
</Paper>
{/* Error Alert */}
{searchError && (
<Alert severity="error">{(searchError as any).message || 'Search failed'}</Alert>
)}
{/* Tabs */}
<Paper sx={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<Tabs
value={tabValue}
onChange={(_, newValue) => setTabValue(newValue)}
indicatorColor="primary"
textColor="primary"
aria-label="stations tabs"
<Box sx={{ padding: 2 }}>
<Grid container spacing={2}>
{/* Map */}
<Grid item xs={12} md={6}>
<Paper
sx={{
height: { xs: 300, md: 520 },
display: 'flex',
overflow: 'hidden'
}}
>
<Tab label={`Results (${searchResults.length})`} id="stations-tab-0" />
<Tab label={`Saved (${savedStations.length})`} id="stations-tab-1" />
</Tabs>
{isMapReady ? (
<GoogleMapsErrorBoundary>
<StationMap
key="desktop-station-map"
stations={searchResults}
savedPlaceIds={savedPlaceIds}
currentLocation={currentLocation}
center={mapCenter || undefined}
height="100%"
readyToRender={true}
/>
</GoogleMapsErrorBoundary>
) : (
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<CircularProgress />
</Box>
)}
</Paper>
</Grid>
{/* Tab Content with overflow */}
<Box sx={{ flex: 1, overflow: 'auto' }}>
<TabPanel value={tabValue} index={0}>
<StationsList
stations={searchResults}
savedPlaceIds={savedPlaceIds}
loading={isSearching}
error={searchError ? (searchError as any).message : null}
onSaveStation={handleSave}
onDeleteStation={handleDelete}
onSelectStation={handleSelectStation}
/>
</TabPanel>
<TabPanel value={tabValue} index={1}>
<SavedStationsList
stations={savedStations}
loading={isSavedLoading}
error={savedError ? (savedError as any).message : null}
onSelectStation={handleSelectStation}
onDeleteStation={handleDelete}
/>
</TabPanel>
</Box>
</Paper>
{/* Search form */}
<Grid item xs={12} md={6}>
<Paper
sx={{
height: { xs: 'auto', md: 520 },
display: 'flex',
flexDirection: 'column',
overflow: 'hidden'
}}
>
<Box sx={{ padding: 2, flex: 1 }}>
<StationsSearchForm onSearch={handleSearch} isSearching={isSearching} />
</Box>
</Paper>
</Grid>
</Grid>
</Grid>
{/* Error Alert */}
{searchError && (
<Box sx={{ mt: 2 }}>
<Alert severity="error">{(searchError as any).message || 'Search failed'}</Alert>
</Box>
)}
{/* Full-width Results */}
<Paper
sx={{
mt: 2,
display: 'flex',
flexDirection: 'column'
}}
>
<Tabs
value={tabValue}
onChange={(_, newValue) => setTabValue(newValue)}
indicatorColor="primary"
textColor="primary"
aria-label="stations tabs"
>
<Tab label={`Results (${searchResults.length})`} id="stations-tab-0" />
<Tab label={`Saved (${savedStations.length})`} id="stations-tab-1" />
</Tabs>
<Box sx={{ flex: 1, overflow: 'auto', padding: 2 }}>
<TabPanel value={tabValue} index={0}>
<StationsList
stations={searchResults}
savedPlaceIds={savedPlaceIds}
savedStationsMap={savedStationsMap}
loading={isSearching}
error={searchError ? (searchError as any).message : null}
onSaveStation={handleSave}
onDeleteStation={handleDelete}
onSelectStation={handleSelectStation}
/>
</TabPanel>
<TabPanel value={tabValue} index={1}>
<SavedStationsList
stations={savedStations}
loading={isSavedLoading}
error={savedError ? (savedError as any).message : null}
onSelectStation={handleSelectStation}
onDeleteStation={handleDelete}
onOctanePreferenceChange={handleOctanePreferenceChange}
octaneUpdatingId={octaneUpdatingId}
/>
</TabPanel>
</Box>
</Paper>
</Box>
);
};