diff --git a/client/src/pages/LitterDetail.jsx b/client/src/pages/LitterDetail.jsx new file mode 100644 index 0000000..ee58a88 --- /dev/null +++ b/client/src/pages/LitterDetail.jsx @@ -0,0 +1,344 @@ +import { useEffect, useState } from 'react' +import { useParams, useNavigate } from 'react-router-dom' +import { ArrowLeft, Plus, X, ExternalLink, Dog } from 'lucide-react' +import axios from 'axios' +import LitterForm from '../components/LitterForm' + +function LitterDetail() { + const { id } = useParams() + const navigate = useNavigate() + const [litter, setLitter] = useState(null) + const [loading, setLoading] = useState(true) + const [showEditForm, setShowEditForm] = useState(false) + const [showAddPuppy, setShowAddPuppy] = useState(false) + const [allDogs, setAllDogs] = useState([]) + const [selectedPuppyId, setSelectedPuppyId] = useState('') + const [newPuppy, setNewPuppy] = useState({ name: '', sex: 'male', color: '', dob: '' }) + const [addMode, setAddMode] = useState('existing') // 'existing' | 'new' + const [error, setError] = useState('') + const [saving, setSaving] = useState(false) + + useEffect(() => { + fetchLitter() + fetchAllDogs() + }, [id]) + + const fetchLitter = async () => { + try { + const res = await axios.get(`/api/litters/${id}`) + setLitter(res.data) + } catch (err) { + console.error('Error fetching litter:', err) + } finally { + setLoading(false) + } + } + + const fetchAllDogs = async () => { + try { + const res = await axios.get('/api/dogs') + setAllDogs(res.data) + } catch (err) { + console.error('Error fetching dogs:', err) + } + } + + const unlinkedDogs = allDogs.filter(d => { + if (!litter) return false + const alreadyInLitter = litter.puppies?.some(p => p.id === d.id) + const isSireOrDam = d.id === litter.sire_id || d.id === litter.dam_id + return !alreadyInLitter && !isSireOrDam + }) + + const handleLinkPuppy = async () => { + if (!selectedPuppyId) return + setSaving(true) + setError('') + try { + await axios.post(`/api/litters/${id}/puppies/${selectedPuppyId}`) + setSelectedPuppyId('') + setShowAddPuppy(false) + fetchLitter() + } catch (err) { + setError(err.response?.data?.error || 'Failed to link puppy') + } finally { + setSaving(false) + } + } + + const handleCreateAndLink = async () => { + if (!newPuppy.name) { + setError('Puppy name is required') + return + } + setSaving(true) + setError('') + try { + const dob = newPuppy.dob || litter.whelping_date || litter.breeding_date + const res = await axios.post('/api/dogs', { + name: newPuppy.name, + sex: newPuppy.sex, + color: newPuppy.color, + date_of_birth: dob, + breed: litter.dam_breed || '', + }) + const createdDog = res.data + await axios.post(`/api/litters/${id}/puppies/${createdDog.id}`) + setNewPuppy({ name: '', sex: 'male', color: '', dob: '' }) + setShowAddPuppy(false) + fetchLitter() + fetchAllDogs() + } catch (err) { + setError(err.response?.data?.error || 'Failed to create puppy') + } finally { + setSaving(false) + } + } + + const handleUnlinkPuppy = async (puppyId) => { + if (!window.confirm('Remove this puppy from the litter? The dog record will not be deleted.')) return + try { + await axios.delete(`/api/litters/${id}/puppies/${puppyId}`) + fetchLitter() + } catch (err) { + console.error('Error unlinking puppy:', err) + } + } + + if (loading) return
Litter not found.
+ Bred: {new Date(litter.breeding_date).toLocaleDateString()} + {litter.whelping_date && ` • Whelped: ${new Date(litter.whelping_date).toLocaleDateString()}`} +
+{litter.notes}
+No puppies linked yet. Add puppies to this litter.
++ No unlinked dogs available. Use "Create New Puppy" instead. +
+ )} ++ Will default to whelping date: {new Date(litter.whelping_date).toLocaleDateString()} +
+ )} +