mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-05-20 22:54:42 +02:00
feat: add editable address field and location display to contacts
This commit is contained in:
parent
1f448b273f
commit
a7519a3aab
1 changed files with 278 additions and 242 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import { X, Trash2 } from 'lucide-react'
|
import { X, Trash2, MapPin } from 'lucide-react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
import { createContact, updateContact, deleteContact, fetchContacts } from '../api'
|
import { createContact, updateContact, deleteContact, fetchContacts, fetchReverse } from '../api'
|
||||||
|
|
||||||
const CATEGORIES = ['family', 'friend', 'business', 'emergency', 'ham', 'bug-out', 'favorite']
|
const CATEGORIES = ['family', 'friend', 'business', 'emergency', 'ham', 'bug-out', 'favorite']
|
||||||
|
|
||||||
|
|
@ -34,6 +34,25 @@ export default function ContactModal() {
|
||||||
}
|
}
|
||||||
}, [editingContact])
|
}, [editingContact])
|
||||||
|
|
||||||
|
// Auto-populate address from reverse geocode when lat/lon exist but address is empty
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editingContact) return
|
||||||
|
const hasGeo = form.lat != null && form.lon != null
|
||||||
|
const addressEmpty = !form.address || form.address.trim() === ''
|
||||||
|
if (hasGeo && addressEmpty) {
|
||||||
|
let cancelled = false
|
||||||
|
fetchReverse(form.lat, form.lon).then((place) => {
|
||||||
|
if (!cancelled && place) {
|
||||||
|
const addr = place.address || place.name || ''
|
||||||
|
if (addr) {
|
||||||
|
setForm((f) => ({ ...f, address: addr }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return () => { cancelled = true }
|
||||||
|
}
|
||||||
|
}, [editingContact, form.lat, form.lon])
|
||||||
|
|
||||||
const close = useCallback(() => clearEditingContact(), [clearEditingContact])
|
const close = useCallback(() => clearEditingContact(), [clearEditingContact])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -46,6 +65,7 @@ export default function ContactModal() {
|
||||||
if (!editingContact) return null
|
if (!editingContact) return null
|
||||||
|
|
||||||
const isEdit = editingContact.id != null
|
const isEdit = editingContact.id != null
|
||||||
|
const hasGeo = form.lat != null && form.lon != null
|
||||||
|
|
||||||
const setField = (key, val) => setForm((f) => ({ ...f, [key]: val }))
|
const setField = (key, val) => setForm((f) => ({ ...f, [key]: val }))
|
||||||
|
|
||||||
|
|
@ -102,8 +122,6 @@ export default function ContactModal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasGeo = form.lat != null && form.lon != null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="contact-modal-overlay" onClick={(e) => { if (e.target === e.currentTarget) close() }}>
|
<div className="contact-modal-overlay" onClick={(e) => { if (e.target === e.currentTarget) close() }}>
|
||||||
<div className="contact-modal">
|
<div className="contact-modal">
|
||||||
|
|
@ -145,6 +163,32 @@ export default function ContactModal() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Address */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="text-xs font-medium mb-1 block" style={{ color: 'var(--text-secondary)' }}>Address</label>
|
||||||
|
<input
|
||||||
|
className="navi-input w-full"
|
||||||
|
value={form.address}
|
||||||
|
onChange={(e) => setField('address', e.target.value)}
|
||||||
|
placeholder="Street address, city, state..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Location */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="text-xs font-medium mb-1 block" style={{ color: 'var(--text-secondary)' }}>Location</label>
|
||||||
|
{hasGeo ? (
|
||||||
|
<div className="flex items-center gap-2 text-xs" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
<MapPin size={12} style={{ color: 'var(--accent)', flexShrink: 0 }} />
|
||||||
|
<span>{form.lat.toFixed(6)}, {form.lon.toFixed(6)}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-xs" style={{ color: 'var(--text-tertiary)' }}>
|
||||||
|
No location — save from a place card to attach coordinates
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Category */}
|
{/* Category */}
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="text-xs font-medium mb-1 block" style={{ color: 'var(--text-secondary)' }}>Category</label>
|
<label className="text-xs font-medium mb-1 block" style={{ color: 'var(--text-secondary)' }}>Category</label>
|
||||||
|
|
@ -206,14 +250,6 @@ export default function ContactModal() {
|
||||||
Show "near {form.label || '...'}" on nearby places
|
Show "near {form.label || '...'}" on nearby places
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{/* Geo info (read-only) */}
|
|
||||||
{hasGeo && (
|
|
||||||
<div className="mb-3 text-xs font-mono" style={{ color: 'var(--text-tertiary)' }}>
|
|
||||||
{form.lat.toFixed(6)}, {form.lon.toFixed(6)}
|
|
||||||
{form.address && <span className="ml-2">{form.address}</span>}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-2 mt-4">
|
<div className="flex gap-2 mt-4">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue