import { useState, useEffect } from 'react' import { Plane, Train, Car, Ship } from 'lucide-react' import Modal from '../shared/Modal' import CustomSelect from '../shared/CustomSelect' import CustomTimePicker from '../shared/CustomTimePicker' import AirportSelect, { type Airport } from './AirportSelect' import LocationSelect, { type LocationPoint } from './LocationSelect' import { useTranslation } from '../../i18n' import { useToast } from '../shared/Toast' import { formatDate } from '../../utils/formatters' import type { Day, Reservation, ReservationEndpoint } from '../../types' const TRANSPORT_TYPES = ['flight', 'train', 'car', 'cruise'] as const type TransportType = typeof TRANSPORT_TYPES[number] interface EndpointPick { airport?: Airport location?: LocationPoint } function endpointFromAirport(a: Airport, role: 'from' | 'to', sequence: number, date: string | null, time: string | null): Omit { return { role, sequence, name: a.city ? `${a.city} (${a.iata})` : a.name, code: a.iata, lat: a.lat, lng: a.lng, timezone: a.tz, local_date: date, local_time: time, } } function endpointFromLocation(l: LocationPoint, role: 'from' | 'to', sequence: number, date: string | null, time: string | null): Omit { return { role, sequence, name: l.name, code: null, lat: l.lat, lng: l.lng, timezone: null, local_date: date, local_time: time, } } function airportFromEndpoint(e: ReservationEndpoint | undefined): Airport | null { if (!e || !e.code) return null return { iata: e.code, icao: null, name: e.name, city: e.name.replace(/\s*\([A-Z]{3}\)\s*$/, ''), country: '', lat: e.lat, lng: e.lng, tz: e.timezone || '', } } function locationFromEndpoint(e: ReservationEndpoint | undefined): LocationPoint | null { if (!e) return null return { name: e.name, lat: e.lat, lng: e.lng, address: null } } const TYPE_OPTIONS = [ { value: 'flight', labelKey: 'reservations.type.flight', Icon: Plane }, { value: 'train', labelKey: 'reservations.type.train', Icon: Train }, { value: 'car', labelKey: 'reservations.type.car', Icon: Car }, { value: 'cruise', labelKey: 'reservations.type.cruise', Icon: Ship }, ] const defaultForm = { title: '', type: 'flight' as TransportType, status: 'pending' as 'pending' | 'confirmed', start_day_id: '' as string | number, end_day_id: '' as string | number, departure_time: '', arrival_time: '', confirmation_number: '', notes: '', meta_airline: '', meta_flight_number: '', meta_train_number: '', meta_platform: '', meta_seat: '', } interface TransportModalProps { isOpen: boolean onClose: () => void onSave: (data: Record) => Promise reservation: Reservation | null days: Day[] selectedDayId: number | null } export function TransportModal({ isOpen, onClose, onSave, reservation, days, selectedDayId }: TransportModalProps) { const { t, locale } = useTranslation() const toast = useToast() const [form, setForm] = useState({ ...defaultForm }) const [isSaving, setIsSaving] = useState(false) const [fromPick, setFromPick] = useState({}) const [toPick, setToPick] = useState({}) useEffect(() => { if (!isOpen) return if (reservation) { const meta = typeof reservation.metadata === 'string' ? JSON.parse(reservation.metadata || '{}') : (reservation.metadata || {}) const eps = reservation.endpoints || [] const from = eps.find(e => e.role === 'from') const to = eps.find(e => e.role === 'to') const type = (TRANSPORT_TYPES as readonly string[]).includes(reservation.type) ? reservation.type as TransportType : 'flight' setForm({ title: reservation.title || '', type, status: reservation.status || 'pending', start_day_id: reservation.day_id ?? '', end_day_id: reservation.end_day_id ?? '', departure_time: reservation.reservation_time?.split('T')[1]?.slice(0, 5) ?? '', arrival_time: reservation.reservation_end_time?.split('T')[1]?.slice(0, 5) ?? '', confirmation_number: reservation.confirmation_number || '', notes: reservation.notes || '', meta_airline: meta.airline || '', meta_flight_number: meta.flight_number || '', meta_train_number: meta.train_number || '', meta_platform: meta.platform || '', meta_seat: meta.seat || '', }) if (type === 'flight') { setFromPick({ airport: airportFromEndpoint(from) || undefined }) setToPick({ airport: airportFromEndpoint(to) || undefined }) } else { setFromPick({ location: locationFromEndpoint(from) || undefined }) setToPick({ location: locationFromEndpoint(to) || undefined }) } } else { setForm({ ...defaultForm, start_day_id: selectedDayId ?? '', end_day_id: selectedDayId ?? '' }) setFromPick({}) setToPick({}) } }, [isOpen, reservation, selectedDayId]) const set = (field: string, value: any) => setForm(prev => ({ ...prev, [field]: value })) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!form.title.trim()) return setIsSaving(true) try { const startDay = days.find(d => d.id === Number(form.start_day_id)) const endDay = days.find(d => d.id === Number(form.end_day_id)) const buildTime = (day: Day | undefined, time: string): string | null => { if (!time) return null return day?.date ? `${day.date}T${time}` : `T${time}` } const metadata: Record = {} if (form.type === 'flight') { if (form.meta_airline) metadata.airline = form.meta_airline if (form.meta_flight_number) metadata.flight_number = form.meta_flight_number if (fromPick.airport) { metadata.departure_airport = fromPick.airport.iata metadata.departure_timezone = fromPick.airport.tz } if (toPick.airport) { metadata.arrival_airport = toPick.airport.iata metadata.arrival_timezone = toPick.airport.tz } } else if (form.type === 'train') { if (form.meta_train_number) metadata.train_number = form.meta_train_number if (form.meta_platform) metadata.platform = form.meta_platform if (form.meta_seat) metadata.seat = form.meta_seat } const startDate = startDay?.date ?? null const endDate = (endDay ?? startDay)?.date ?? null const endpoints: ReturnType[] = [] if (form.type === 'flight') { if (fromPick.airport) endpoints.push(endpointFromAirport(fromPick.airport, 'from', 0, startDate, form.departure_time || null)) if (toPick.airport) endpoints.push(endpointFromAirport(toPick.airport, 'to', 1, endDate, form.arrival_time || null)) } else { if (fromPick.location) endpoints.push(endpointFromLocation(fromPick.location, 'from', 0, startDate, form.departure_time || null)) if (toPick.location) endpoints.push(endpointFromLocation(toPick.location, 'to', 1, endDate, form.arrival_time || null)) } const payload = { title: form.title, type: form.type, status: form.status, day_id: form.start_day_id ? Number(form.start_day_id) : null, end_day_id: form.end_day_id ? Number(form.end_day_id) : null, reservation_time: buildTime(startDay, form.departure_time), reservation_end_time: buildTime(endDay ?? startDay, form.arrival_time), location: null, confirmation_number: form.confirmation_number || null, notes: form.notes || null, metadata: Object.keys(metadata).length > 0 ? metadata : null, endpoints, needs_review: false, } await onSave(payload) } catch (err: unknown) { toast.error(err instanceof Error ? err.message : t('common.unknownError')) } finally { setIsSaving(false) } } const inputStyle = { width: '100%', border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px', fontSize: 13, fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' as const, color: 'var(--text-primary)', background: 'var(--bg-input)', } const labelStyle = { display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--text-faint)', marginBottom: 5, textTransform: 'uppercase' as const, letterSpacing: '0.03em', } const dayOptions = [ { value: '', label: '—' }, ...days.map(d => { const dateBadge = d.date ? (formatDate(d.date, locale) ?? undefined) : undefined const dayBadge = d.title ? t('dayplan.dayN', { n: d.day_number }) : undefined return { value: d.id, label: d.title || t('dayplan.dayN', { n: d.day_number }), badge: dateBadge ?? dayBadge, } }), ] return (
{/* Type selector */}
{TYPE_OPTIONS.map(({ value, labelKey, Icon }) => ( ))}
{/* Title */}
set('title', e.target.value)} required placeholder={t('reservations.titlePlaceholder')} style={inputStyle} />
{/* From / To endpoints */}
{form.type === 'flight' ? ( setFromPick({ airport: a || undefined })} /> ) : ( setFromPick({ location: l || undefined })} /> )}
{form.type === 'flight' ? ( setToPick({ airport: a || undefined })} /> ) : ( setToPick({ location: l || undefined })} /> )}
{/* Departure row */}
set('start_day_id', value)} placeholder={t('dayplan.dayN', { n: '?' })} options={dayOptions} size="sm" />
set('departure_time', v)} />
{form.type === 'flight' && fromPick.airport && (
{fromPick.airport.tz}
)}
{/* Arrival row */}
set('end_day_id', value)} placeholder={t('dayplan.dayN', { n: '?' })} options={dayOptions} size="sm" />
set('arrival_time', v)} />
{form.type === 'flight' && toPick.airport && (
{toPick.airport.tz}
)}
{/* Flight-specific fields */} {form.type === 'flight' && (
set('meta_airline', e.target.value)} placeholder="Lufthansa" style={inputStyle} />
set('meta_flight_number', e.target.value)} placeholder="LH 123" style={inputStyle} />
)} {/* Train-specific fields */} {form.type === 'train' && (
set('meta_train_number', e.target.value)} placeholder="ICE 123" style={inputStyle} />
set('meta_platform', e.target.value)} placeholder="12" style={inputStyle} />
set('meta_seat', e.target.value)} placeholder="42A" style={inputStyle} />
)} {/* Booking Code + Status */}
set('confirmation_number', e.target.value)} placeholder={t('reservations.confirmationPlaceholder')} style={inputStyle} />
set('status', value)} options={[ { value: 'pending', label: t('reservations.pending') }, { value: 'confirmed', label: t('reservations.confirmed') }, ]} size="sm" />
{/* Notes */}