import React, { useState, useEffect } from 'react' import Modal from '../shared/Modal' import { mapsApi, tagsApi, categoriesApi } from '../../api/client' import { useToast } from '../shared/Toast' import { useAuthStore } from '../../store/authStore' import { useTranslation } from '../../i18n' import { Search, Plus, MapPin, Loader } from 'lucide-react' const STATUSES = [ { value: 'none', label: 'None' }, { value: 'pending', label: 'Pending' }, { value: 'confirmed', label: 'Confirmed' }, ] export default function PlaceFormModal({ isOpen, onClose, onSave, place, tripId, categories: initialCategories = [], tags: initialTags = [], onCategoryCreated, onTagCreated, }) { const isEditing = !!place const { user, hasMapsKey } = useAuthStore() const { t } = useTranslation() const toast = useToast() const [categories, setCategories] = useState(initialCategories) const [tags, setTags] = useState(initialTags) useEffect(() => { setCategories(initialCategories) }, [initialCategories]) useEffect(() => { setTags(initialTags) }, [initialTags]) const emptyForm = { name: '', description: '', address: '', lat: '', lng: '', category_id: '', place_time: '', reservation_status: 'none', reservation_notes: '', reservation_datetime: '', google_place_id: '', website: '', tags: [], } const [formData, setFormData] = useState(emptyForm) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState('') // Maps search state const [mapQuery, setMapQuery] = useState('') const [mapResults, setMapResults] = useState([]) const [mapSearching, setMapSearching] = useState(false) // New category/tag const [newCategoryName, setNewCategoryName] = useState('') const [newCategoryColor, setNewCategoryColor] = useState('#374151') const [showNewCategory, setShowNewCategory] = useState(false) const [newTagName, setNewTagName] = useState('') const [newTagColor, setNewTagColor] = useState('#374151') const [showNewTag, setShowNewTag] = useState(false) useEffect(() => { if (place && isOpen) { setFormData({ name: place.name || '', description: place.description || '', address: place.address || '', lat: place.lat ?? '', lng: place.lng ?? '', category_id: place.category_id || '', place_time: place.place_time || '', reservation_status: place.reservation_status || 'none', reservation_notes: place.reservation_notes || '', reservation_datetime: place.reservation_datetime || '', google_place_id: place.google_place_id || '', website: place.website || '', tags: (place.tags || []).map(t => t.id), }) } else if (!place && isOpen) { setFormData(emptyForm) } setError('') setMapResults([]) setMapQuery('') }, [place, isOpen]) const update = (field, value) => setFormData(prev => ({ ...prev, [field]: value })) const toggleTag = (tagId) => { setFormData(prev => ({ ...prev, tags: prev.tags.includes(tagId) ? prev.tags.filter(id => id !== tagId) : [...prev.tags, tagId] })) } const handleSubmit = async (e) => { e.preventDefault() if (!formData.name.trim()) { setError('Place name is required') return } setIsLoading(true) setError('') try { await onSave({ ...formData, lat: formData.lat !== '' ? parseFloat(formData.lat) : null, lng: formData.lng !== '' ? parseFloat(formData.lng) : null, category_id: formData.category_id || null, }) onClose() } catch (err) { setError(err.message || 'Failed to save place') } finally { setIsLoading(false) } } const [searchSource, setSearchSource] = useState(null) const handleMapSearch = async () => { if (!mapQuery.trim()) return setMapSearching(true) try { const data = await mapsApi.search(mapQuery) setMapResults(data.places || []) setSearchSource(data.source || 'google') } catch (err) { toast.error(err.response?.data?.error || t('places.mapsSearchError')) } finally { setMapSearching(false) } } const selectMapPlace = (p) => { setFormData(prev => ({ ...prev, name: p.name || prev.name, address: p.address || prev.address, lat: p.lat ?? prev.lat, lng: p.lng ?? prev.lng, google_place_id: p.google_place_id || prev.google_place_id, website: p.website || prev.website, })) setMapResults([]) setMapQuery('') } const handleCreateCategory = async () => { if (!newCategoryName.trim()) return try { const data = await categoriesApi.create({ name: newCategoryName, color: newCategoryColor, icon: 'MapPin' }) setCategories(prev => [...prev, data.category]) if (onCategoryCreated) onCategoryCreated(data.category) setFormData(prev => ({ ...prev, category_id: data.category.id })) setNewCategoryName('') setShowNewCategory(false) toast.success('Category created') } catch (err) { toast.error('Failed to create category') } } const handleCreateTag = async () => { if (!newTagName.trim()) return try { const data = await tagsApi.create({ name: newTagName, color: newTagColor }) setTags(prev => [...prev, data.tag]) if (onTagCreated) onTagCreated(data.tag) setFormData(prev => ({ ...prev, tags: [...prev.tags, data.tag.id] })) setNewTagName('') setShowNewTag(false) toast.success('Tag created') } catch (err) { toast.error('Failed to create tag') } } return ( } >
{error && (
{error}
)} {/* Place search — Google Maps or OpenStreetMap fallback */}
{!hasMapsKey && (

{t('places.osmActive')}

)}
setMapQuery(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleMapSearch()} placeholder={t('places.mapsSearchPlaceholder')} className="w-full pl-8 pr-3 py-2 text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-slate-400 focus:border-transparent bg-white" />
{mapResults.length > 0 && (
{mapResults.map((p, i) => ( ))}
)}
{/* Name */}
update('name', e.target.value)} required placeholder="e.g. Eiffel Tower" className="w-full px-3 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent" />
{/* Description */}