import { useState, useEffect, useRef } from 'react' import Modal from '../shared/Modal' import { Calendar, Camera, X, Clipboard } from 'lucide-react' import { tripsApi } from '../../api/client' import { useToast } from '../shared/Toast' import { useTranslation } from '../../i18n' import { CustomDatePicker } from '../shared/CustomDateTimePicker' import type { Trip } from '../../types' interface TripFormModalProps { isOpen: boolean onClose: () => void onSave: (data: Record) => Promise | void trip: Trip | null onCoverUpdate: (tripId: number, coverUrl: string) => void } export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUpdate }: TripFormModalProps) { const isEditing = !!trip const fileRef = useRef(null) const toast = useToast() const { t } = useTranslation() const [formData, setFormData] = useState({ title: '', description: '', start_date: '', end_date: '', }) const [error, setError] = useState('') const [isLoading, setIsLoading] = useState(false) const [coverPreview, setCoverPreview] = useState(null) const [pendingCoverFile, setPendingCoverFile] = useState(null) const [uploadingCover, setUploadingCover] = useState(false) useEffect(() => { if (trip) { setFormData({ title: trip.title || '', description: trip.description || '', start_date: trip.start_date || '', end_date: trip.end_date || '', }) setCoverPreview(trip.cover_image || null) } else { setFormData({ title: '', description: '', start_date: '', end_date: '' }) setCoverPreview(null) } setPendingCoverFile(null) setError('') }, [trip, isOpen]) const handleSubmit = async (e) => { e.preventDefault() setError('') if (!formData.title.trim()) { setError(t('dashboard.titleRequired')); return } if (formData.start_date && formData.end_date && new Date(formData.end_date) < new Date(formData.start_date)) { setError(t('dashboard.endDateError')); return } setIsLoading(true) try { const result = await onSave({ title: formData.title.trim(), description: formData.description.trim() || null, start_date: formData.start_date || null, end_date: formData.end_date || null, }) // Upload pending cover for newly created trips if (pendingCoverFile && result?.trip?.id) { try { const fd = new FormData() fd.append('cover', pendingCoverFile) const data = await tripsApi.uploadCover(result.trip.id, fd) onCoverUpdate?.(result.trip.id, data.cover_image) } catch { // Cover upload failed but trip was created } } onClose() } catch (err: unknown) { setError(err instanceof Error ? err.message : t('places.saveError')) } finally { setIsLoading(false) } } const handleCoverSelect = (file) => { if (!file) return if (isEditing && trip?.id) { // Existing trip: upload immediately uploadCoverNow(file) } else { // New trip: stage for upload after creation setPendingCoverFile(file) setCoverPreview(URL.createObjectURL(file)) } } const handleCoverChange = (e) => { handleCoverSelect((e.target as HTMLInputElement).files?.[0]) e.target.value = '' } const uploadCoverNow = async (file) => { setUploadingCover(true) try { const fd = new FormData() fd.append('cover', file) const data = await tripsApi.uploadCover(trip.id, fd) setCoverPreview(data.cover_image) onCoverUpdate?.(trip.id, data.cover_image) toast.success(t('dashboard.coverSaved')) } catch { toast.error(t('dashboard.coverUploadError')) } finally { setUploadingCover(false) } } const handleRemoveCover = async () => { if (pendingCoverFile) { setPendingCoverFile(null) setCoverPreview(null) return } if (!trip?.id) return try { await tripsApi.update(trip.id, { cover_image: null }) setCoverPreview(null) onCoverUpdate?.(trip.id, null) } catch { toast.error(t('dashboard.coverRemoveError')) } } // Paste support for cover image const handlePaste = (e) => { const items = e.clipboardData?.items if (!items) return for (const item of Array.from(items)) { if (item.type.startsWith('image/')) { e.preventDefault() const file = item.getAsFile() if (file) handleCoverSelect(file) return } } } const update = (field, value) => setFormData(prev => { const next = { ...prev, [field]: value } if (field === 'start_date' && value) { if (!prev.end_date || prev.end_date < value) { next.end_date = value } else if (prev.start_date) { const oldStart = new Date(prev.start_date + 'T00:00:00') const oldEnd = new Date(prev.end_date + 'T00:00:00') const duration = Math.round((oldEnd - oldStart) / 86400000) const newEnd = new Date(value + 'T00:00:00') newEnd.setDate(newEnd.getDate() + duration) next.end_date = newEnd.toISOString().split('T')[0] } } return next }) const inputCls = "w-full px-3 py-2.5 border border-slate-200 rounded-lg text-slate-900 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-300 focus:border-transparent text-sm" return ( } >
{error && (
{error}
)} {/* Cover image — available for both create and edit */}
{coverPreview ? (
) : ( )}
update('title', e.target.value)} required placeholder={t('dashboard.tripTitlePlaceholder')} className={inputCls} />