diff --git a/src/components/admin/ParkForm.tsx b/src/components/admin/ParkForm.tsx index ac68c527..d9161e09 100644 --- a/src/components/admin/ParkForm.tsx +++ b/src/components/admin/ParkForm.tsx @@ -11,7 +11,11 @@ import { UppyPhotoUpload } from '@/components/upload/UppyPhotoUpload'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { DatePicker } from '@/components/ui/date-picker'; import { toast } from '@/hooks/use-toast'; -import { MapPin, Save, X } from 'lucide-react'; +import { MapPin, Save, X, Plus } from 'lucide-react'; +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'; const parkSchema = z.object({ name: z.string().min(1, 'Park name is required'), @@ -23,15 +27,32 @@ const parkSchema = z.object({ closing_date: z.string().optional(), website_url: z.string().url().optional().or(z.literal('')), phone: z.string().optional(), - email: z.string().email().optional().or(z.literal('')) + email: z.string().email().optional().or(z.literal('')), + operator_id: z.string().uuid().optional(), + property_owner_id: z.string().uuid().optional() }); type ParkFormData = z.infer; interface ParkFormProps { - onSubmit: (data: ParkFormData & { banner_image_url?: string; card_image_url?: string; banner_image_id?: string; card_image_id?: string }) => Promise; + onSubmit: (data: ParkFormData & { + banner_image_url?: string; + card_image_url?: string; + banner_image_id?: string; + card_image_id?: string; + operator_id?: string; + property_owner_id?: string; + _compositeSubmission?: any; + }) => Promise; onCancel?: () => void; - initialData?: Partial; + initialData?: Partial; isEditing?: boolean; } @@ -61,6 +82,20 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: const [cardImage, setCardImage] = useState(initialData?.card_image_url || ''); const [bannerImageId, setBannerImageId] = useState(initialData?.banner_image_id || ''); const [cardImageId, setCardImageId] = useState(initialData?.card_image_id || ''); + + // 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); + + // Fetch data + const { operators, loading: operatorsLoading } = useOperators(); + const { propertyOwners, loading: ownersLoading } = usePropertyOwners(); // Extract Cloudflare image ID from URL const extractImageId = (url: string): string => { @@ -86,7 +121,9 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: closing_date: initialData?.closing_date || '', website_url: initialData?.website_url || '', phone: initialData?.phone || '', - email: initialData?.email || '' + email: initialData?.email || '', + operator_id: initialData?.operator_id || undefined, + property_owner_id: initialData?.property_owner_id || undefined } }); @@ -108,12 +145,38 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: const handleFormSubmit = async (data: ParkFormData) => { setSubmitting(true); try { + // Build composite submission if new entities were created + const submissionContent: any = { + park: { + ...data, + banner_image_url: bannerImage || undefined, + card_image_url: cardImage || undefined, + banner_image_id: bannerImageId || undefined, + card_image_id: cardImageId || undefined + }, + }; + + // Add new operator if created + if (tempNewOperator) { + submissionContent.new_operator = tempNewOperator; + submissionContent.park.operator_id = null; + } + + // Add new property owner if created + if (tempNewPropertyOwner) { + submissionContent.new_property_owner = tempNewPropertyOwner; + submissionContent.park.property_owner_id = null; + } + await onSubmit({ ...data, banner_image_url: bannerImage || undefined, card_image_url: cardImage || undefined, banner_image_id: bannerImageId || undefined, - card_image_id: cardImageId || undefined + card_image_id: cardImageId || undefined, + operator_id: tempNewOperator ? undefined : (selectedOperatorId || undefined), + property_owner_id: tempNewPropertyOwner ? undefined : (selectedPropertyOwnerId || undefined), + _compositeSubmission: (tempNewOperator || tempNewPropertyOwner) ? submissionContent : undefined }); toast({ @@ -251,6 +314,105 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: + {/* Operator & Property Owner Selection */} +
+

Operator & Property Owner

+ +
+ {/* Operator Column */} +
+ + + {tempNewOperator ? ( +
+ New + {tempNewOperator.name} + +
+ ) : ( + { + setValue('operator_id', value); + setSelectedOperatorId(value); + }} + placeholder="Select operator" + searchPlaceholder="Search operators..." + emptyText="No operators found" + loading={operatorsLoading} + /> + )} + + {!tempNewOperator && ( + + )} +
+ + {/* Property Owner Column */} +
+ + + {tempNewPropertyOwner ? ( +
+ New + {tempNewPropertyOwner.name} + +
+ ) : ( + { + setValue('property_owner_id', value); + setSelectedPropertyOwnerId(value); + }} + placeholder="Select property owner" + searchPlaceholder="Search property owners..." + emptyText="No property owners found" + loading={ownersLoading} + /> + )} + + {!tempNewPropertyOwner && ( + + )} +
+
+
+ {/* Contact Information */}
@@ -373,6 +535,32 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: )}
+ + {/* Operator Modal - Placeholder */} + + + + Create New Operator + + Add a new park operator company + + +

Operator form coming soon...

+
+
+ + {/* Property Owner Modal - Placeholder */} + + + + Create New Property Owner + + Add a new park property owner company + + +

Property owner form coming soon...

+
+
); diff --git a/src/hooks/useAutocompleteData.ts b/src/hooks/useAutocompleteData.ts index 77612fe6..3c58d48a 100644 --- a/src/hooks/useAutocompleteData.ts +++ b/src/hooks/useAutocompleteData.ts @@ -200,4 +200,76 @@ export function useCompanyHeadquarters() { }, []); return { headquarters, loading }; +} + +export function useOperators() { + const [operators, setOperators] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + async function fetchOperators() { + setLoading(true); + try { + const { data, error } = await supabase + .from('companies') + .select('id, name') + .eq('company_type', 'operator') + .order('name'); + + if (error) throw error; + + setOperators( + (data || []).map(company => ({ + label: company.name, + value: company.id + })) + ); + } catch (error) { + console.error('Error fetching operators:', error); + setOperators([]); + } finally { + setLoading(false); + } + } + + fetchOperators(); + }, []); + + return { operators, loading }; +} + +export function usePropertyOwners() { + const [propertyOwners, setPropertyOwners] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + async function fetchPropertyOwners() { + setLoading(true); + try { + const { data, error } = await supabase + .from('companies') + .select('id, name') + .eq('company_type', 'property_owner') + .order('name'); + + if (error) throw error; + + setPropertyOwners( + (data || []).map(company => ({ + label: company.name, + value: company.id + })) + ); + } catch (error) { + console.error('Error fetching property owners:', error); + setPropertyOwners([]); + } finally { + setLoading(false); + } + } + + fetchPropertyOwners(); + }, []); + + return { propertyOwners, loading }; } \ No newline at end of file diff --git a/src/pages/ParkDetail.tsx b/src/pages/ParkDetail.tsx index 0d1d8be0..c851cd1a 100644 --- a/src/pages/ParkDetail.tsx +++ b/src/pages/ParkDetail.tsx @@ -177,23 +177,29 @@ export default function ParkDetail() { try { if (isModerator()) { // Moderators can update directly + const updateData: any = { + name: parkData.name, + slug: parkData.slug, + description: parkData.description || null, + park_type: parkData.park_type, + status: parkData.status, + opening_date: parkData.opening_date || null, + closing_date: parkData.closing_date || null, + website_url: parkData.website_url || null, + phone: parkData.phone || null, + email: parkData.email || null, + banner_image_url: parkData.banner_image_url || null, + banner_image_id: parkData.banner_image_id || null, + card_image_url: parkData.card_image_url || null, + card_image_id: parkData.card_image_id || null, + operator_id: parkData.operator_id || null, + property_owner_id: parkData.property_owner_id || null + }; + const { error } = await supabase .from('parks') .update({ - name: parkData.name, - slug: parkData.slug, - description: parkData.description, - park_type: parkData.park_type, - status: parkData.status, - opening_date: parkData.opening_date || null, - closing_date: parkData.closing_date || null, - website_url: parkData.website_url || null, - phone: parkData.phone || null, - email: parkData.email || null, - banner_image_url: parkData.banner_image_url || null, - banner_image_id: parkData.banner_image_id || null, - card_image_url: parkData.card_image_url || null, - card_image_id: parkData.card_image_id || null, + ...updateData, updated_at: new Date().toISOString() }) .eq('id', park.id); @@ -663,7 +669,9 @@ export default function ParkDetail() { banner_image_url: park?.banner_image_url, banner_image_id: park?.banner_image_id, card_image_url: park?.card_image_url, - card_image_id: park?.card_image_id + card_image_id: park?.card_image_id, + operator_id: park?.operator?.id, + property_owner_id: park?.property_owner?.id }} isEditing={true} /> diff --git a/src/pages/Parks.tsx b/src/pages/Parks.tsx index dffb33ce..f41ab984 100644 --- a/src/pages/Parks.tsx +++ b/src/pages/Parks.tsx @@ -268,7 +268,9 @@ export default function Parks() { banner_image_url: parkData.banner_image_url || null, banner_image_id: parkData.banner_image_id || null, card_image_url: parkData.card_image_url || null, - card_image_id: parkData.card_image_id || null + card_image_id: parkData.card_image_id || null, + operator_id: parkData.operator_id || null, + property_owner_id: parkData.property_owner_id || null }); if (error) throw error;