Google Maps Bug
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user