import { useState, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { useQuery } from '@tanstack/react-query'; import { X, AlertTriangle, Users, Car, Link2 } from 'lucide-react'; import { api } from '@/lib/api'; import { ScheduleEvent, VIP, Driver, Vehicle } from '@/types'; import { useFormattedDate } from '@/hooks/useFormattedDate'; import { toDatetimeLocal } from '@/lib/utils'; import { EVENT_TYPE_LABELS, EVENT_STATUS_LABELS } from '@/lib/enum-labels'; import { queryKeys } from '@/lib/query-keys'; interface EventFormProps { event?: ScheduleEvent | null; onSubmit: (data: EventFormData) => void; onCancel: () => void; isSubmitting: boolean; extraActions?: React.ReactNode; } export interface EventFormData { vipIds: string[]; title: string; location?: string; pickupLocation?: string; dropoffLocation?: string; startTime: string; endTime: string; description?: string; type: string; status: string; driverId?: string; vehicleId?: string; masterEventId?: string; forceAssign?: boolean; } interface ScheduleConflict { id: string; title: string; startTime: string; endTime: string; } export function EventForm({ event, onSubmit, onCancel, isSubmitting, extraActions }: EventFormProps) { const { formatDateTime } = useFormattedDate(); const [formData, setFormData] = useState({ vipIds: event?.vipIds || [], title: event?.title || '', location: event?.location || '', pickupLocation: event?.pickupLocation || '', dropoffLocation: event?.dropoffLocation || '', startTime: toDatetimeLocal(event?.startTime), endTime: toDatetimeLocal(event?.endTime), description: event?.description || '', type: event?.type || 'TRANSPORT', status: event?.status || 'SCHEDULED', driverId: event?.driverId || '', vehicleId: event?.vehicleId || '', masterEventId: event?.masterEventId || '', }); const [showConflictDialog, setShowConflictDialog] = useState(false); const [conflicts, setConflicts] = useState([]); const [showCapacityWarning, setShowCapacityWarning] = useState(false); const [capacityExceeded, setCapacityExceeded] = useState<{capacity: number, requested: number} | null>(null); const [pendingData, setPendingData] = useState(null); // Fetch VIPs for selection const { data: vips } = useQuery({ queryKey: queryKeys.vips.all, queryFn: async () => { const { data } = await api.get('/vips'); return data; }, }); // Fetch Drivers for dropdown const { data: drivers } = useQuery({ queryKey: queryKeys.drivers.all, queryFn: async () => { const { data } = await api.get('/drivers'); return data; }, }); // Fetch Vehicles for dropdown const { data: vehicles } = useQuery({ queryKey: queryKeys.vehicles.all, queryFn: async () => { const { data } = await api.get('/vehicles'); return data; }, }); // Fetch all events (for master event selector) const { data: allEvents } = useQuery({ queryKey: queryKeys.events.all, queryFn: async () => { const { data } = await api.get('/events'); return data; }, }); // Filter to itinerary items (non-transport events) for master event dropdown const masterEventOptions = useMemo(() => { if (!allEvents) return []; return allEvents.filter(e => e.type !== 'TRANSPORT' && e.status !== 'CANCELLED' && e.id !== event?.id // Exclude self ); }, [allEvents, event?.id]); // Get selected vehicle capacity (using party sizes) const selectedVehicle = vehicles?.find(v => v.id === formData.vehicleId); const seatsUsed = vips ?.filter(v => formData.vipIds.includes(v.id)) .reduce((sum, v) => sum + (v.partySize || 1), 0) || 0; const seatsAvailable = selectedVehicle ? selectedVehicle.seatCapacity : 0; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // Clean up the data const cleanedData: EventFormData = { ...formData, startTime: new Date(formData.startTime).toISOString(), endTime: new Date(formData.endTime).toISOString(), location: formData.location || undefined, pickupLocation: formData.pickupLocation || undefined, dropoffLocation: formData.dropoffLocation || undefined, description: formData.description || undefined, driverId: formData.driverId || undefined, vehicleId: formData.vehicleId || undefined, masterEventId: formData.masterEventId || undefined, }; // Store for potential retry setPendingData(cleanedData); // Submit directly handleActualSubmit(cleanedData); }; const handleActualSubmit = async (data: EventFormData) => { try { await onSubmit(data); // Success handled by parent component } catch (error: any) { console.error('[EVENT_FORM] Submit failed:', error); // Check for conflict error if (error.response?.data?.conflicts) { setConflicts(error.response.data.conflicts); setShowConflictDialog(true); return; } // Check for capacity error if (error.response?.data?.exceeded) { setCapacityExceeded({ capacity: error.response.data.capacity, requested: error.response.data.requested, }); setShowCapacityWarning(true); return; } // Other errors are shown via toast by parent } }; const handleForceSubmit = () => { if (pendingData) { const dataWithForce = { ...pendingData, forceAssign: true }; setShowConflictDialog(false); setShowCapacityWarning(false); handleActualSubmit(dataWithForce); } }; const handleCancelConflict = () => { setShowConflictDialog(false); setShowCapacityWarning(false); setPendingData(null); setConflicts([]); setCapacityExceeded(null); }; const handleChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value, })); }; const handleVipToggle = (vipId: string) => { setFormData((prev) => { const isSelected = prev.vipIds.includes(vipId); return { ...prev, vipIds: isSelected ? prev.vipIds.filter(id => id !== vipId) : [...prev.vipIds, vipId], }; }); }; const selectedVipNames = useMemo(() => { return vips ?.filter(vip => formData.vipIds.includes(vip.id)) .map(vip => vip.partySize > 1 ? `${vip.name} (+${vip.partySize - 1})` : vip.name) .join(', ') || 'None selected'; }, [vips, formData.vipIds]); return ( <>

{event ? 'Edit Event' : 'Add New Event'}

{/* Master Event Selector (link transport to itinerary item) */} {formData.type === 'TRANSPORT' && masterEventOptions.length > 0 && (

Link this transport to a shared activity. VIPs will auto-populate from the linked event.

)} {/* VIP Multi-Select */}
{vips?.map((vip) => ( ))}
Selected ({formData.vipIds.length}): {selectedVipNames}
{/* Title */}
{/* Pickup & Dropoff Locations */}
{/* Start & End Time */}
{/* Vehicle Selection with Capacity */}
{selectedVehicle && (
seatsAvailable ? 'text-red-600 font-medium' : 'text-muted-foreground'}`}> Capacity: {seatsUsed}/{seatsAvailable} seats (incl. entourage) {seatsUsed > seatsAvailable && ' — OVER CAPACITY'}
)}
{/* Driver Selection */}
{/* Event Type & Status */}
{/* Description */}