import { useEffect, useState } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { ArrowLeft, Plus, X, ExternalLink, Dog, Weight, ChevronDown, ChevronUp, Trash2 } from 'lucide-react' import axios from 'axios' import LitterForm from '../components/LitterForm' // ─── Puppy Log Panel ──────────────────────────────────────────────────────────── function PuppyLogPanel({ litterId, puppy, whelpingDate }) { const [open, setOpen] = useState(false) const [logs, setLogs] = useState([]) const [loading, setLoading] = useState(false) const [showAdd, setShowAdd] = useState(false) const [form, setForm] = useState({ record_date: whelpingDate || '', weight_oz: '', weight_lbs: '', notes: '', record_type: 'weight_log' }) const [saving, setSaving] = useState(false) useEffect(() => { if (open) fetchLogs() }, [open]) const fetchLogs = async () => { setLoading(true) try { const res = await axios.get(`/api/litters/${litterId}/puppies/${puppy.id}/logs`) const parsed = res.data.map(l => { try { return { ...l, _data: JSON.parse(l.description) } } catch { return { ...l, _data: {} } } }) setLogs(parsed) } catch (e) { console.error(e) } finally { setLoading(false) } } const handleAdd = async () => { if (!form.record_date) return setSaving(true) try { await axios.post(`/api/litters/${litterId}/puppies/${puppy.id}/logs`, form) setShowAdd(false) setForm(f => ({ ...f, weight_oz: '', weight_lbs: '', notes: '' })) fetchLogs() } catch (e) { console.error(e) } finally { setSaving(false) } } const handleDelete = async (logId) => { if (!window.confirm('Delete this log entry?')) return try { await axios.delete(`/api/litters/${litterId}/puppies/${puppy.id}/logs/${logId}`) fetchLogs() } catch (e) { console.error(e) } } const TYPES = [ { value: 'weight_log', label: '⚖️ Weight Check' }, { value: 'health_note', label: '📝 Health Note' }, { value: 'deworming', label: '🐛 Deworming' }, { value: 'vaccination', label: '💉 Vaccination' }, ] return (
{open && (
{loading ? (

Loading...

) : logs.length === 0 ? (

No logs yet.

) : (
{logs.map(l => (
{new Date(l.record_date + 'T00:00:00').toLocaleDateString()} {' • '} {TYPES.find(t => t.value === l.record_type)?.label || l.record_type} {l._data?.weight_oz && — {l._data.weight_oz} oz} {l._data?.weight_lbs && ({l._data.weight_lbs} lbs)} {l._data?.notes && (
{l._data.notes}
)}
))}
)} {showAdd ? (
setForm(f => ({ ...f, record_date: e.target.value }))} />
{form.record_type === 'weight_log' && (
setForm(f => ({ ...f, weight_oz: e.target.value }))} /> setForm(f => ({ ...f, weight_lbs: e.target.value }))} />
)} setForm(f => ({ ...f, notes: e.target.value }))} />
) : ( )}
)}
) } // ─── Whelping Window Banner ─────────────────────────────────────────────── function addDays(dateStr, n) { const d = new Date(dateStr + 'T00:00:00') d.setDate(d.getDate() + n) return d } function fmt(d) { return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) } function WhelpingBanner({ breedingDate, whelpingDate }) { if (whelpingDate) return null // already whelped, no need for estimate if (!breedingDate) return null const earliest = addDays(breedingDate, 58) const expected = addDays(breedingDate, 63) const latest = addDays(breedingDate, 68) const today = new Date() const daysUntil = Math.ceil((expected - today) / 86400000) let urgency = 'var(--success)' let urgencyBg = 'rgba(16,185,129,0.06)' let statusLabel = `~${daysUntil} days away` if (daysUntil <= 7 && daysUntil > 0) { urgency = '#d97706'; urgencyBg = 'rgba(217,119,6,0.08)' statusLabel = `⚠️ ${daysUntil} days — prepare whelping area!` } else if (daysUntil <= 0) { urgency = '#e53e3e'; urgencyBg = 'rgba(229,62,62,0.08)' statusLabel = '🔴 Expected date has passed — confirm or update whelping date' } return (
💕 Projected Whelping Window {statusLabel}
Earliest (Day 58)
{fmt(earliest)}
Expected (Day 63)
{fmt(expected)}
Latest (Day 68)
{fmt(latest)}
) } // ─── Main LitterDetail ───────────────────────────────────────────────────────── 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') 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 || '', }) await axios.post(`/api/litters/${id}/puppies/${res.data.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
Loading litter...
if (!litter) return

Litter not found.

const puppyCount = litter.puppies?.length ?? 0 return (
{/* Header */}

🐾 {litter.sire_name} × {litter.dam_name}

Bred: {new Date(litter.breeding_date + 'T00:00:00').toLocaleDateString()} {litter.whelping_date && ` • Whelped: ${new Date(litter.whelping_date + 'T00:00:00').toLocaleDateString()}`}

{/* Stats row */}
{puppyCount}
Puppies Linked
{litter.puppies?.filter(p => p.sex === 'male').length ?? 0}
Males
{litter.puppies?.filter(p => p.sex === 'female').length ?? 0}
Females
{litter.puppy_count > 0 && (
{litter.puppy_count}
Expected
)}
{/* Projected whelping window */} {/* Notes */} {litter.notes && (

{litter.notes}

)} {/* Puppies section */}

Puppies

{puppyCount === 0 ? (

No puppies linked yet. Add puppies to this litter.

) : (
{litter.puppies.map(puppy => (
{puppy.sex === 'male' ? '🐦' : '🐥'}
{puppy.name}
{puppy.sex} {puppy.color && `• ${puppy.color}`}
{puppy.date_of_birth && (
Born: {new Date(puppy.date_of_birth + 'T00:00:00').toLocaleDateString()}
)} {/* Weight / Health Log collapsible */}
))}
)} {/* Add Puppy Modal */} {showAddPuppy && (
setShowAddPuppy(false)}>
e.stopPropagation()} style={{ maxWidth: '480px' }}>

Add Puppy to Litter

{error &&
{error}
}
{addMode === 'existing' ? (
{unlinkedDogs.length === 0 && (

No unlinked dogs available. Use "Create New Puppy" instead.

)}
) : (
setNewPuppy(p => ({ ...p, name: e.target.value }))} placeholder="e.g. Blue Collar" />
setNewPuppy(p => ({ ...p, color: e.target.value }))} placeholder="e.g. Black & Tan" />
setNewPuppy(p => ({ ...p, dob: e.target.value }))} /> {litter.whelping_date && !newPuppy.dob && (

Will default to whelping date: {new Date(litter.whelping_date + 'T00:00:00').toLocaleDateString()}

)}
)}
)} {showEditForm && ( setShowEditForm(false)} onSave={fetchLitter} /> )}
) } export default LitterDetail