feat: require add mode before map click opens modal
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { MapContainer, Marker, Popup, TileLayer, useMapEvents } from 'react-leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { LeafletMouseEvent } from 'leaflet';
|
||||
import { ensureLeafletConfig } from './leafletConfig';
|
||||
import { useVisitModalStore } from '../../state/useVisitModalStore';
|
||||
@@ -14,13 +14,20 @@ import AddIcon from '@mui/icons-material/Add';
|
||||
const INITIAL_POSITION: [number, number] = [20, 0];
|
||||
const INITIAL_ZOOM = 2;
|
||||
|
||||
function MapEvents() {
|
||||
const openForCreate = useVisitModalStore((state) => state.openForCreate);
|
||||
interface MapEventsProps {
|
||||
enabled: boolean;
|
||||
onSelect: (location: { lat: number; lng: number }) => void;
|
||||
}
|
||||
|
||||
function MapEvents({ enabled, onSelect }: MapEventsProps) {
|
||||
useMapEvents({
|
||||
click: (event: LeafletMouseEvent) => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lat, lng } = event.latlng;
|
||||
openForCreate({ lat, lng });
|
||||
onSelect({ lat, lng });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -43,6 +50,25 @@ export function TravelMap({ onTriggerCreate, sidebarOpen = true }: TravelMapProp
|
||||
const { t } = useTranslation();
|
||||
const mapRef = useRef<LeafletMap | null>(null);
|
||||
useResizeMap(mapRef, [sidebarOpen]);
|
||||
const [isAwaitingLocation, setIsAwaitingLocation] = useState(false);
|
||||
|
||||
const handleMapSelection = useCallback(
|
||||
({ lat, lng }: { lat: number; lng: number }) => {
|
||||
setIsAwaitingLocation(false);
|
||||
|
||||
if (onTriggerCreate) {
|
||||
onTriggerCreate({ lat, lng });
|
||||
return;
|
||||
}
|
||||
|
||||
openForCreate({ lat, lng });
|
||||
},
|
||||
[onTriggerCreate, openForCreate]
|
||||
);
|
||||
|
||||
const handleFabClick = useCallback(() => {
|
||||
setIsAwaitingLocation((previous) => !previous);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box sx={{ position: 'relative', height: '100%', width: '100%' }}>
|
||||
@@ -51,7 +77,7 @@ export function TravelMap({ onTriggerCreate, sidebarOpen = true }: TravelMapProp
|
||||
mapRef.current = mapInstance;
|
||||
mapInstance.invalidateSize();
|
||||
}}
|
||||
className="h-full w-full z-0"
|
||||
className={`h-full w-full z-0 ${isAwaitingLocation ? 'cursor-crosshair' : ''}`}
|
||||
center={INITIAL_POSITION}
|
||||
zoom={INITIAL_ZOOM}
|
||||
scrollWheelZoom
|
||||
@@ -60,7 +86,7 @@ export function TravelMap({ onTriggerCreate, sidebarOpen = true }: TravelMapProp
|
||||
attribution='\u00a9 <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<MapEvents />
|
||||
<MapEvents enabled={isAwaitingLocation} onSelect={handleMapSelection} />
|
||||
{visits.map((visit) => (
|
||||
<Marker key={visit.id} position={[visit.location.lat, visit.location.lng]}>
|
||||
<Popup>
|
||||
@@ -78,9 +104,10 @@ export function TravelMap({ onTriggerCreate, sidebarOpen = true }: TravelMapProp
|
||||
</MapContainer>
|
||||
<Tooltip title={t('sidebar.add')} placement="left">
|
||||
<Fab
|
||||
color="primary"
|
||||
color={isAwaitingLocation ? 'secondary' : 'primary'}
|
||||
sx={{ position: 'absolute', bottom: 24, right: 24, zIndex: 1000 }}
|
||||
onClick={() => (onTriggerCreate ? onTriggerCreate() : openForCreate())}
|
||||
onClick={handleFabClick}
|
||||
aria-pressed={isAwaitingLocation}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, Grid, Paper, Typography } from '@mui/material';
|
||||
import { Box, Card, CardContent, Typography } from '@mui/material';
|
||||
import { useVisitsQuery } from '../hooks/useVisitQueries';
|
||||
|
||||
export function VisitSummary() {
|
||||
@@ -22,25 +22,32 @@ export function VisitSummary() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
<CardContent>
|
||||
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||
<CardContent sx={{ py: 1.5, px: 2 }}>
|
||||
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
||||
{t('summary.title')}
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
overflowX: 'auto',
|
||||
pb: 0.5
|
||||
}}
|
||||
>
|
||||
{summaryItems.map((item) => (
|
||||
<Grid item xs={6} key={item.label}>
|
||||
<Paper elevation={0} sx={{ p: 2, bgcolor: 'grey.100', textAlign: 'center' }}>
|
||||
<Typography variant="overline" display="block" color="text.secondary">
|
||||
{item.label}
|
||||
</Typography>
|
||||
<Typography variant="h5" fontWeight={700} color="text.primary">
|
||||
{item.value}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Box key={item.label} sx={{ flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{item.label}
|
||||
</Typography>
|
||||
<Typography variant="h6" fontWeight={700} color="text.primary">
|
||||
{item.value}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "My Journeys",
|
||||
"subtitle": "Click anywhere on the map to start recording",
|
||||
"subtitle": "Tap the + button, then click the map to start recording",
|
||||
"add": "Add Visit",
|
||||
"drawerTitleCreate": "Add Visit",
|
||||
"drawerTitleEdit": "Edit Visit",
|
||||
|
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "我的旅程",
|
||||
"subtitle": "點擊地圖任意位置開始記錄",
|
||||
"subtitle": "先點右下角的+按鈕,再點地圖任一處開始記錄",
|
||||
"add": "新增足跡",
|
||||
"drawerTitleCreate": "新增足跡",
|
||||
"drawerTitleEdit": "編輯足跡",
|
||||
|
Reference in New Issue
Block a user