diff --git a/client/src/components/city/LocationPicker.tsx b/client/src/components/city/LocationPicker.tsx new file mode 100644 index 0000000..a8c625f --- /dev/null +++ b/client/src/components/city/LocationPicker.tsx @@ -0,0 +1,44 @@ +import 'leaflet/dist/leaflet.css'; +import { useMemo, useRef } from 'react'; +import { MapContainer, TileLayer, Marker } from 'react-leaflet'; +import type L from 'leaflet'; +import type { CityStatus } from '@citytracker/shared'; +import { cityMarkerIcon } from '../map/CityMarker'; + +interface LocationPickerProps { + lat: number; + lng: number; + status: CityStatus; + onChange: (lat: number, lng: number) => void; +} + +export function LocationPicker({ lat, lng, status, onChange }: LocationPickerProps) { + const icon = useMemo(() => cityMarkerIcon(status), [status]); + const markerRef = useRef(null); + + return ( +
+ + + { + const marker = markerRef.current; + if (!marker) return; + const { lat: newLat, lng: newLng } = marker.getLatLng(); + onChange(newLat, newLng); + }, + }} + /> + +

Drag the pin to update the location.

+
+ ); +} diff --git a/client/src/components/map/CityMap.tsx b/client/src/components/map/CityMap.tsx index 944e8b4..b1c5379 100644 --- a/client/src/components/map/CityMap.tsx +++ b/client/src/components/map/CityMap.tsx @@ -43,7 +43,7 @@ export function CityMap({ cities, focusCity }: CityMapProps) { /> {cities.map((city) => ( - +
{city.name} diff --git a/client/src/components/map/CityMarker.tsx b/client/src/components/map/CityMarker.tsx index f31efca..c7d0f0b 100644 --- a/client/src/components/map/CityMarker.tsx +++ b/client/src/components/map/CityMarker.tsx @@ -1,8 +1,8 @@ import L from 'leaflet'; -import type { City } from '@citytracker/shared'; +import type { CityStatus } from '@citytracker/shared'; -export function cityMarkerIcon(city: City): L.DivIcon { - const color = city.status === 'visited' ? '#2f9e44' : '#1971c2'; +export function cityMarkerIcon(status: CityStatus): L.DivIcon { + const color = status === 'visited' ? '#2f9e44' : '#1971c2'; return L.divIcon({ className: 'city-marker', html: ``, diff --git a/client/src/pages/CityDetailPage.tsx b/client/src/pages/CityDetailPage.tsx index 24493cc..1d9b144 100644 --- a/client/src/pages/CityDetailPage.tsx +++ b/client/src/pages/CityDetailPage.tsx @@ -6,6 +6,7 @@ import { useAuth } from '../context/AuthContext'; import { useCity, useDeleteCity, useUploadPhoto, useDeletePhoto } from '../hooks/useCities'; import * as citiesApi from '../api/cities'; import { CitySearchInput } from '../components/city/CitySearchInput'; +import { LocationPicker } from '../components/city/LocationPicker'; import { LinkListEditor } from '../components/city/LinkListEditor'; import { ParticipantPicker } from '../components/city/ParticipantPicker'; import { PhotoUploader } from '../components/city/PhotoUploader'; @@ -189,6 +190,14 @@ export function CityDetailPage({ mode }: CityDetailPageProps) { } } + function handleLocationChange(lat: number, lng: number) { + setForm((prev) => { + const next: FormState = { ...prev, lat, lng }; + scheduleSave(next, true); + return next; + }); + } + function handleGeocodeSelect(result: GeocodeResult) { setForm((prev) => { const next: FormState = { @@ -249,9 +258,21 @@ export function CityDetailPage({ mode }: CityDetailPageProps) {
{form.lat !== null && form.lng !== null && ( -

- Location: {form.lat.toFixed(4)}, {form.lng.toFixed(4)} -

+ <> + {isEditing ? ( + + ) : ( +

+ Location: {form.lat.toFixed(4)}, {form.lng.toFixed(4)} +

+ )} + )}