import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { validateSubmissionHandler } from '@/lib/entityFormValidation'; import type { RideTechnicalSpec, RideCoasterStat, RideNameHistory } from '@/types/database'; import { entitySchemas } from '@/lib/entityValidationSchemas'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { EntityMultiImageUploader, ImageAssignments } from '@/components/upload/EntityMultiImageUploader'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { DatePicker } from '@/components/ui/date-picker'; import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-date-input'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Combobox } from '@/components/ui/combobox'; import { SlugField } from '@/components/ui/slug-field'; import { toast } from '@/hooks/use-toast'; import { Plus, Zap, Save, X } from 'lucide-react'; import { toDateOnly } from '@/lib/dateUtils'; import { useUnitPreferences } from '@/hooks/useUnitPreferences'; import { useManufacturers, useRideModels } from '@/hooks/useAutocompleteData'; import { useUserRole } from '@/hooks/useUserRole'; import { ManufacturerForm } from './ManufacturerForm'; import { RideModelForm } from './RideModelForm'; import { TechnicalSpecsEditor } from './editors/TechnicalSpecsEditor'; import { CoasterStatsEditor } from './editors/CoasterStatsEditor'; import { FormerNamesEditor } from './editors/FormerNamesEditor'; import { convertValueToMetric, convertValueFromMetric, getDisplayUnit, getSpeedUnit, getDistanceUnit, getHeightUnit } from '@/lib/units'; type RideFormData = z.infer; interface RideFormProps { onSubmit: (data: RideFormData) => Promise; onCancel?: () => void; initialData?: Partial; isEditing?: boolean; } const categories = [ 'roller_coaster', 'flat_ride', 'water_ride', 'dark_ride', 'kiddie_ride', 'transportation' ]; const statusOptions = [ 'Operating', 'Seasonal', 'Closed Temporarily', 'Closed Permanently', 'Under Construction', 'Planned', 'SBNO' // Standing But Not Operating ]; const coasterTypes = [ 'steel', 'wood', 'hybrid' ]; const seatingTypes = [ 'sit_down', 'stand_up', 'flying', 'inverted', 'floorless', 'suspended', 'wing', 'dive', 'spinning' ]; const intensityLevels = [ 'family', 'thrill', 'extreme' ]; // Status value mappings between display (form) and database values const STATUS_DISPLAY_TO_DB: Record = { 'Operating': 'operating', 'Seasonal': 'operating', 'Closed Temporarily': 'maintenance', 'Closed Permanently': 'closed', 'Under Construction': 'under_construction', 'Planned': 'under_construction', 'SBNO': 'sbno' }; const STATUS_DB_TO_DISPLAY: Record = { 'operating': 'Operating', 'closed': 'Closed Permanently', 'under_construction': 'Under Construction', 'maintenance': 'Closed Temporarily', 'sbno': 'SBNO' }; export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: RideFormProps) { const { isModerator } = useUserRole(); const [submitting, setSubmitting] = useState(false); const { preferences } = useUnitPreferences(); const measurementSystem = preferences.measurement_system; // Validate that onSubmit uses submission helpers (dev mode only) useEffect(() => { validateSubmissionHandler(onSubmit, 'ride'); }, [onSubmit]); // Manufacturer and model state const [selectedManufacturerId, setSelectedManufacturerId] = useState( initialData?.manufacturer_id || '' ); const [selectedManufacturerName, setSelectedManufacturerName] = useState(''); const [tempNewManufacturer, setTempNewManufacturer] = useState(null); const [tempNewRideModel, setTempNewRideModel] = useState(null); const [isManufacturerModalOpen, setIsManufacturerModalOpen] = useState(false); const [isModelModalOpen, setIsModelModalOpen] = useState(false); // Advanced editor state - using simplified interface for editors (DB fields added on submit) const [technicalSpecs, setTechnicalSpecs] = useState<{ spec_name: string; spec_value: string; spec_type: 'string' | 'number' | 'boolean' | 'date'; category?: string; unit?: string; display_order: number; }[]>([]); const [coasterStats, setCoasterStats] = useState<{ stat_name: string; stat_value: number; unit?: string; category?: string; description?: string; display_order: number; }[]>([]); const [formerNames, setFormerNames] = useState<{ former_name: string; date_changed?: Date | null; reason?: string; from_year?: number; to_year?: number; order_index: number; }[]>([]); // Fetch data const { manufacturers, loading: manufacturersLoading } = useManufacturers(); const { rideModels, loading: modelsLoading } = useRideModels(selectedManufacturerId); const { register, handleSubmit, setValue, watch, formState: { errors } } = useForm({ resolver: zodResolver(entitySchemas.ride), defaultValues: { name: initialData?.name || '', slug: initialData?.slug || '', description: initialData?.description || '', category: initialData?.category || '', ride_sub_type: initialData?.ride_sub_type || '', status: initialData?.status ? STATUS_DB_TO_DISPLAY[initialData.status] || 'Operating' : 'Operating', opening_date: initialData?.opening_date || '', opening_date_precision: initialData?.opening_date_precision || 'day', closing_date: initialData?.closing_date || '', closing_date_precision: initialData?.closing_date_precision || 'day', // Convert metric values to user's preferred unit for display height_requirement: initialData?.height_requirement ? convertValueFromMetric(initialData.height_requirement, getDisplayUnit('cm', measurementSystem), 'cm') : undefined, age_requirement: initialData?.age_requirement || undefined, capacity_per_hour: initialData?.capacity_per_hour || undefined, duration_seconds: initialData?.duration_seconds || undefined, max_speed_kmh: initialData?.max_speed_kmh ? convertValueFromMetric(initialData.max_speed_kmh, getDisplayUnit('km/h', measurementSystem), 'km/h') : undefined, max_height_meters: initialData?.max_height_meters ? convertValueFromMetric(initialData.max_height_meters, getDisplayUnit('m', measurementSystem), 'm') : undefined, length_meters: initialData?.length_meters ? convertValueFromMetric(initialData.length_meters, getDisplayUnit('m', measurementSystem), 'm') : undefined, inversions: initialData?.inversions || undefined, coaster_type: initialData?.coaster_type || undefined, seating_type: initialData?.seating_type || undefined, intensity_level: initialData?.intensity_level || undefined, drop_height_meters: initialData?.drop_height_meters ? convertValueFromMetric(initialData.drop_height_meters, getDisplayUnit('m', measurementSystem), 'm') : undefined, max_g_force: initialData?.max_g_force || undefined, manufacturer_id: initialData?.manufacturer_id || undefined, ride_model_id: initialData?.ride_model_id || undefined, images: { uploaded: [] } } }); const selectedCategory = watch('category'); const handleFormSubmit = async (data: RideFormData) => { setSubmitting(true); try { // Transform status from display value to DB value const dbStatus = STATUS_DISPLAY_TO_DB[data.status] || 'operating'; // Convert form values back to metric for storage const metricData = { ...data, status: dbStatus, height_requirement: data.height_requirement ? convertValueToMetric(data.height_requirement, getDisplayUnit('cm', measurementSystem)) : undefined, max_speed_kmh: data.max_speed_kmh ? convertValueToMetric(data.max_speed_kmh, getDisplayUnit('km/h', measurementSystem)) : undefined, max_height_meters: data.max_height_meters ? convertValueToMetric(data.max_height_meters, getDisplayUnit('m', measurementSystem)) : undefined, length_meters: data.length_meters ? convertValueToMetric(data.length_meters, getDisplayUnit('m', measurementSystem)) : undefined, drop_height_meters: data.drop_height_meters ? convertValueToMetric(data.drop_height_meters, getDisplayUnit('m', measurementSystem)) : undefined, // Pass relational data for proper handling _technical_specifications: technicalSpecs, _coaster_statistics: coasterStats, _name_history: formerNames, _tempNewManufacturer: tempNewManufacturer, _tempNewRideModel: tempNewRideModel }; // Pass clean data to parent with extended fields await onSubmit(metricData); toast({ title: isEditing ? "Ride Updated" : "Submission Sent", description: isEditing ? "The ride information has been updated successfully." : tempNewManufacturer ? "Ride, manufacturer, and model submitted for review" : "Ride submitted for review" }); } catch (error: any) { toast({ title: "Error", description: error.message || "Failed to save ride information.", variant: "destructive" }); } finally { setSubmitting(false); } }; return ( {isEditing ? 'Edit Ride' : 'Create New Ride'}
{/* Basic Information */}
{errors.name && (

{errors.name.message}

)}
setValue('slug', slug)} isModerator={isModerator()} />
{/* Description */}