import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Species } from '@/services/adminApi'; // Validation schema const speciesSchema = z.object({ scientific_name: z.string().min(1, 'Scientific name is required'), common_name: z.string().min(1, 'Common name is required'), kingdom: z.string().min(1, 'Kingdom is required'), phylum: z.string().min(1, 'Phylum is required'), class: z.string().min(1, 'Class is required'), order_name: z.string().min(1, 'Order is required'), family: z.string().min(1, 'Family is required'), genus: z.string().min(1, 'Genus is required'), species_name: z.string().min(1, 'Species is required'), authority: z.string(), // No longer optional, allows empty string sis_id: z.preprocess((val) => (val === "" || val === null ? undefined : val), z.coerce.number().int().positive().optional()), inaturalist_id: z.preprocess((val) => (val === "" || val === null ? undefined : val), z.coerce.number().int().positive().optional()), default_image_url: z.string().url().optional().or(z.string().length(0)), description: z.string().optional(), habitat_description: z.string().optional(), population_trend: z.string().optional(), population_size: z.preprocess((val) => (val === "" || val === null ? undefined : val), z.coerce.number().optional()), generation_length: z.preprocess((val) => (val === "" || val === null ? undefined : val), z.coerce.number().optional()), movement_patterns: z.string().optional(), use_and_trade: z.string().optional(), threats_overview: z.string().optional(), conservation_overview: z.string().optional(), }); interface SpeciesFormProps { initialData?: Species; onSubmit: (data: Species) => void; isSubmitting: boolean; } export function SpeciesForm({ initialData, onSubmit, isSubmitting }: SpeciesFormProps) { // Start with the description tab if the species has a description const initialTab = initialData?.description ? 'description' : 'basic'; const [activeTab, setActiveTab] = useState(initialTab); // Prepare default values, converting nulls from initialData to empty strings // for optional fields to align with Zod's expectation (string | undefined) const getSafeDefaultValue = (value: string | null | undefined) => value === null ? '' : value; const getSafeNumericDefaultValue = (value: number | string | null | undefined) => { if (value === null || value === undefined || value === '') { return undefined; } const num = Number(value); return isNaN(num) ? undefined : num; }; const form = useForm>({ resolver: zodResolver(speciesSchema), defaultValues: initialData ? { ...initialData, authority: getSafeDefaultValue(initialData.authority), default_image_url: getSafeDefaultValue(initialData.default_image_url), description: getSafeDefaultValue(initialData.description), habitat_description: getSafeDefaultValue(initialData.habitat_description), population_trend: getSafeDefaultValue(initialData.population_trend), // For numeric fields, ensure they are numbers or undefined sis_id: getSafeNumericDefaultValue(initialData.sis_id), inaturalist_id: getSafeNumericDefaultValue(initialData.inaturalist_id), population_size: getSafeNumericDefaultValue(initialData.population_size), generation_length: getSafeNumericDefaultValue(initialData.generation_length), movement_patterns: getSafeDefaultValue(initialData.movement_patterns), use_and_trade: getSafeDefaultValue(initialData.use_and_trade), threats_overview: getSafeDefaultValue(initialData.threats_overview), conservation_overview: getSafeDefaultValue(initialData.conservation_overview), } : { scientific_name: '', common_name: '', kingdom: 'Animalia', phylum: '', class: '', order_name: '', family: '', genus: '', species_name: '', authority: '', sis_id: undefined, inaturalist_id: undefined, default_image_url: '', description: '', habitat_description: '', population_trend: '', // Default to empty string, Select handles it population_size: undefined, generation_length: undefined, movement_patterns: '', use_and_trade: '', threats_overview: '', conservation_overview: '', }, }); // If initialData changes (e.g., after a fetch), reset the form with new processed defaults. // This is important if the component re-renders with new initialData after the first mount. useEffect(() => { if (initialData) { form.reset({ ...initialData, authority: getSafeDefaultValue(initialData.authority), default_image_url: getSafeDefaultValue(initialData.default_image_url), description: getSafeDefaultValue(initialData.description), habitat_description: getSafeDefaultValue(initialData.habitat_description), population_trend: getSafeDefaultValue(initialData.population_trend), sis_id: getSafeNumericDefaultValue(initialData.sis_id), inaturalist_id: getSafeNumericDefaultValue(initialData.inaturalist_id), population_size: getSafeNumericDefaultValue(initialData.population_size), generation_length: getSafeNumericDefaultValue(initialData.generation_length), movement_patterns: getSafeDefaultValue(initialData.movement_patterns), use_and_trade: getSafeDefaultValue(initialData.use_and_trade), threats_overview: getSafeDefaultValue(initialData.threats_overview), conservation_overview: getSafeDefaultValue(initialData.conservation_overview), }); } }, [initialData, form.reset]); // form.reset is a stable function reference const handleSubmit = (values: z.infer) => { // Process values to ensure optional empty strings are sent as empty strings, // or convert to null if your backend prefers null for empty optional text fields. // The current Zod schema and Supabase text fields generally handle empty strings fine. const processedValues = { ...values, description: values.description || '', habitat_description: values.habitat_description || '', population_trend: values.population_trend || '', // Keep as empty string if that's what select gives movement_patterns: values.movement_patterns || '', use_and_trade: values.use_and_trade || '', threats_overview: values.threats_overview || '', conservation_overview: values.conservation_overview || '', // Ensure numeric fields are sent as null if undefined, or their numeric value sis_id: values.sis_id === undefined ? null : values.sis_id, inaturalist_id: values.inaturalist_id === undefined ? null : values.inaturalist_id, population_size: values.population_size === undefined ? null : values.population_size, generation_length: values.generation_length === undefined ? null : values.generation_length, }; onSubmit(processedValues as Species); }; return (
Basic Info Taxonomy Description Conservation {/* Basic Info Tab */}
( Scientific Name* Full scientific name with genus and species )} /> ( Common Name* Primary English common name )} />
( IUCN SIS ID field.onChange(e.target.value === '' ? undefined : parseInt(e.target.value))} placeholder="e.g. 22823" /> Species Information Service ID from IUCN )} /> ( iNaturalist ID field.onChange(e.target.value === '' ? undefined : parseInt(e.target.value))} placeholder="e.g. 42021" /> Taxon ID from iNaturalist )} />
( Default Image URL URL for the main species image )} />
{/* Taxonomy Tab */}
( Kingdom* )} /> ( Phylum* )} />
( Class* )} /> ( Order* )} />
( Family* )} /> ( Genus* )} />
( Species* Specific epithet (species part of the binomial name) )} /> ( Authority Name and year of the authority who described the species )} />
{/* Description Tab */} ( General Description