import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { entitySchemas, validateRequiredFields } from '@/lib/entityValidationSchemas'; import { validateSubmissionHandler } from '@/lib/entityFormValidation'; import { getErrorMessage } from '@/lib/errorHandler'; 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 { 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 { SlugField } from '@/components/ui/slug-field'; import { toast } from '@/hooks/use-toast'; import { handleError } from '@/lib/errorHandler'; import { MapPin, Save, X, Plus, AlertCircle } from 'lucide-react'; import { toDateOnly, parseDateOnly, toDateWithPrecision } from '@/lib/dateUtils'; import { Badge } from '@/components/ui/badge'; import { Combobox } from '@/components/ui/combobox'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { useOperators, usePropertyOwners } from '@/hooks/useAutocompleteData'; import { useUserRole } from '@/hooks/useUserRole'; import { useAuth } from '@/hooks/useAuth'; import type { TempCompanyData } from '@/types/company'; import { LocationSearch } from './LocationSearch'; import { OperatorForm } from './OperatorForm'; import { PropertyOwnerForm } from './PropertyOwnerForm'; import { Checkbox } from '@/components/ui/checkbox'; const parkSchema = z.object({ name: z.string().min(1, 'Park name is required'), slug: z.string().min(1, 'Slug is required'), // Auto-generated, validated on submit description: z.string().optional(), park_type: z.string().min(1, 'Park type is required'), status: z.string().min(1, 'Status is required'), opening_date: z.string().optional().transform(val => val || undefined), opening_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).optional(), closing_date: z.string().optional().transform(val => val || undefined), closing_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).optional(), location: z.object({ name: z.string(), street_address: z.string().optional(), city: z.string().optional(), state_province: z.string().optional(), country: z.string(), postal_code: z.string().optional(), latitude: z.number(), longitude: z.number(), timezone: z.string().optional(), display_name: z.string(), }).optional(), location_id: z.string().uuid().optional(), website_url: z.string().url().optional().or(z.literal('')), phone: z.string().optional(), email: z.string().email().optional().or(z.literal('')), operator_id: z.string().uuid().optional().or(z.literal('')).transform(val => val || undefined), property_owner_id: z.string().uuid().optional().or(z.literal('')).transform(val => val || undefined), source_url: z.string().url().optional().or(z.literal('')), submission_notes: z.string().max(1000).optional().or(z.literal('')), images: z.object({ uploaded: z.array(z.object({ url: z.string(), cloudflare_id: z.string().optional(), file: z.instanceof(File).optional(), isLocal: z.boolean().optional(), caption: z.string().optional(), })), banner_assignment: z.number().nullable().optional(), card_assignment: z.number().nullable().optional(), }).optional() }); type ParkFormData = z.infer; interface ParkFormProps { onSubmit: (data: ParkFormData & { operator_id?: string; property_owner_id?: string; _compositeSubmission?: import('@/types/composite-submission').ParkCompositeSubmission; }) => Promise; onCancel?: () => void; initialData?: Partial; isEditing?: boolean; } const parkTypes = [ { value: 'theme_park', label: 'Theme Park' }, { value: 'amusement_park', label: 'Amusement Park' }, { value: 'water_park', label: 'Water Park' }, { value: 'family_entertainment', label: 'Family Entertainment Center' }, { value: 'adventure_park', label: 'Adventure Park' }, { value: 'safari_park', label: 'Safari Park' }, { value: 'carnival', label: 'Carnival' }, { value: 'fair', label: 'Fair' } ]; const statusOptions = [ 'Operating', 'Closed Temporarily', 'Closed Permanently', 'Under Construction', 'Planned', 'Abandoned' ]; // Status mappings const STATUS_DISPLAY_TO_DB: Record = { 'Operating': 'operating', 'Closed Temporarily': 'closed_temporarily', 'Closed Permanently': 'closed_permanently', 'Under Construction': 'under_construction', 'Planned': 'planned', 'Abandoned': 'abandoned' }; const STATUS_DB_TO_DISPLAY: Record = { 'operating': 'Operating', 'closed_temporarily': 'Closed Temporarily', 'closed_permanently': 'Closed Permanently', 'under_construction': 'Under Construction', 'planned': 'Planned', 'abandoned': 'Abandoned' }; export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: ParkFormProps) { const { isModerator } = useUserRole(); // Validate that onSubmit uses submission helpers (dev mode only) useEffect(() => { validateSubmissionHandler(onSubmit, 'park'); }, [onSubmit]); const { user } = useAuth(); const [isSubmitting, setIsSubmitting] = useState(false); // Operator state const [selectedOperatorId, setSelectedOperatorId] = useState(initialData?.operator_id || ''); const [tempNewOperator, setTempNewOperator] = useState(null); const [isOperatorModalOpen, setIsOperatorModalOpen] = useState(false); // Property Owner state const [selectedPropertyOwnerId, setSelectedPropertyOwnerId] = useState(initialData?.property_owner_id || ''); const [tempNewPropertyOwner, setTempNewPropertyOwner] = useState(null); const [isPropertyOwnerModalOpen, setIsPropertyOwnerModalOpen] = useState(false); // Operator is Owner checkbox state const [operatorIsOwner, setOperatorIsOwner] = useState( !!(initialData?.operator_id && initialData?.property_owner_id && initialData?.operator_id === initialData?.property_owner_id) ); // Fetch data const { operators, loading: operatorsLoading } = useOperators(); const { propertyOwners, loading: ownersLoading } = usePropertyOwners(); const { register, handleSubmit, setValue, watch, trigger, formState: { errors } } = useForm({ resolver: zodResolver(entitySchemas.park), defaultValues: { name: initialData?.name || '', slug: initialData?.slug || '', description: initialData?.description || '', park_type: initialData?.park_type || '', status: initialData?.status || 'operating' as const, // Store DB value opening_date: initialData?.opening_date || undefined, closing_date: initialData?.closing_date || undefined, location_id: initialData?.location_id || undefined, website_url: initialData?.website_url || '', phone: initialData?.phone || '', email: initialData?.email || '', operator_id: initialData?.operator_id || undefined, property_owner_id: initialData?.property_owner_id || undefined, source_url: initialData?.source_url || '', submission_notes: initialData?.submission_notes || '', images: { uploaded: [] } } }); // Sync property owner with operator when checkbox is enabled useEffect(() => { if (operatorIsOwner && selectedOperatorId) { setSelectedPropertyOwnerId(selectedOperatorId); setValue('property_owner_id', selectedOperatorId); } }, [operatorIsOwner, selectedOperatorId, setValue]); const handleFormSubmit = async (data: ParkFormData) => { setIsSubmitting(true); try { // Pre-submission validation for required fields const { valid, errors: validationErrors } = validateRequiredFields('park', data); if (!valid) { validationErrors.forEach(error => { toast({ variant: 'destructive', title: 'Missing Required Fields', description: error }); }); setIsSubmitting(false); return; } // CRITICAL: Block new photo uploads on edits if (isEditing && data.images?.uploaded) { const hasNewPhotos = data.images.uploaded.some(img => img.isLocal); if (hasNewPhotos) { toast({ variant: 'destructive', title: 'Validation Error', description: 'New photos cannot be added during edits. Please remove new photos or use the photo gallery.' }); return; } } // Build composite submission if new entities were created const submissionContent: import('@/types/composite-submission').ParkCompositeSubmission = { park: data, }; // Add new operator if created if (tempNewOperator) { submissionContent.new_operator = tempNewOperator; submissionContent.park.operator_id = null; // If operator is also owner, use same entity for both if (operatorIsOwner) { submissionContent.new_property_owner = tempNewOperator; submissionContent.park.property_owner_id = null; } } // Add new property owner if created (and not already set above) if (tempNewPropertyOwner && !operatorIsOwner) { submissionContent.new_property_owner = tempNewPropertyOwner; submissionContent.park.property_owner_id = null; } // Determine final IDs to pass // When creating new entities via composite submission, IDs should be undefined // When using existing entities, pass their IDs directly let finalOperatorId: string | undefined; let finalPropertyOwnerId: string | undefined; if (tempNewOperator) { // New operator being created via composite submission finalOperatorId = undefined; finalPropertyOwnerId = operatorIsOwner ? undefined : (tempNewPropertyOwner ? undefined : selectedPropertyOwnerId); } else { // Using existing operator finalOperatorId = selectedOperatorId || undefined; finalPropertyOwnerId = operatorIsOwner ? finalOperatorId : (tempNewPropertyOwner ? undefined : selectedPropertyOwnerId); } // Debug: Log what's being submitted const submissionData = { ...data, operator_id: finalOperatorId, property_owner_id: finalPropertyOwnerId, _compositeSubmission: (tempNewOperator || tempNewPropertyOwner) ? submissionContent : undefined }; console.info('[ParkForm] Submitting park data:', { hasLocation: !!submissionData.location, hasLocationId: !!submissionData.location_id, locationData: submissionData.location, parkName: submissionData.name, isEditing }); await onSubmit(submissionData); // Parent component handles success feedback } catch (error: unknown) { const errorMessage = getErrorMessage(error); handleError(error, { action: isEditing ? 'Update Park' : 'Create Park', userId: user?.id, metadata: { parkName: data.name, hasLocation: !!data.location_id, hasNewOperator: !!tempNewOperator, hasNewOwner: !!tempNewPropertyOwner } }); // Re-throw so parent can handle modal closing throw error; } finally { setIsSubmitting(false); } }; return ( {isEditing ? 'Edit Park' : 'Create New Park'}
{/* Basic Information */}
{errors.name && (

{errors.name.message}

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