feat: rebuild LitterList with create/edit/delete and LitterForm integration

This commit is contained in:
2026-03-09 20:49:34 -05:00
parent da0e61ee98
commit 0e8b875a4c

View File

@@ -1,10 +1,16 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Activity } from 'lucide-react' import { Activity, Plus, Edit2, Trash2, ChevronRight } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios' import axios from 'axios'
import LitterForm from '../components/LitterForm'
function LitterList() { function LitterList() {
const [litters, setLitters] = useState([]) const [litters, setLitters] = useState([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [showForm, setShowForm] = useState(false)
const [editingLitter, setEditingLitter] = useState(null)
const [prefill, setPrefill] = useState(null)
const navigate = useNavigate()
useEffect(() => { useEffect(() => {
fetchLitters() fetchLitters()
@@ -14,47 +20,126 @@ function LitterList() {
try { try {
const res = await axios.get('/api/litters') const res = await axios.get('/api/litters')
setLitters(res.data) setLitters(res.data)
setLoading(false)
} catch (error) { } catch (error) {
console.error('Error fetching litters:', error) console.error('Error fetching litters:', error)
} finally {
setLoading(false) setLoading(false)
} }
} }
const handleCreate = () => {
setEditingLitter(null)
setPrefill(null)
setShowForm(true)
}
const handleEdit = (e, litter) => {
e.stopPropagation()
setEditingLitter(litter)
setPrefill(null)
setShowForm(true)
}
const handleDelete = async (e, id) => {
e.stopPropagation()
if (!window.confirm('Delete this litter record? Puppies will be unlinked but not deleted.')) return
try {
await axios.delete(`/api/litters/${id}`)
fetchLitters()
} catch (error) {
console.error('Error deleting litter:', error)
}
}
const handleSave = () => {
fetchLitters()
}
if (loading) { if (loading) {
return <div className="container loading">Loading litters...</div> return <div className="container loading">Loading litters...</div>
} }
return ( return (
<div className="container"> <div className="container">
<h1 style={{ marginBottom: '2rem' }}>Litters</h1> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
<h1>Litters</h1>
<button className="btn btn-primary" onClick={handleCreate}>
<Plus size={18} style={{ marginRight: '0.5rem' }} />
New Litter
</button>
</div>
{litters.length === 0 ? ( {litters.length === 0 ? (
<div className="card" style={{ textAlign: 'center', padding: '4rem' }}> <div className="card" style={{ textAlign: 'center', padding: '4rem' }}>
<Activity size={64} style={{ color: 'var(--text-secondary)', margin: '0 auto 1rem' }} /> <Activity size={64} style={{ color: 'var(--text-secondary)', margin: '0 auto 1rem' }} />
<h2>No litters recorded yet</h2> <h2>No litters recorded yet</h2>
<p style={{ color: 'var(--text-secondary)' }}>Start tracking breeding records</p> <p style={{ color: 'var(--text-secondary)', marginBottom: '1.5rem' }}>Create a litter after a breeding cycle to track puppies</p>
<button className="btn btn-primary" onClick={handleCreate}>
<Plus size={18} style={{ marginRight: '0.5rem' }} />
Create First Litter
</button>
</div> </div>
) : ( ) : (
<div style={{ display: 'grid', gap: '1rem' }}> <div style={{ display: 'grid', gap: '1rem' }}>
{litters.map(litter => ( {litters.map(litter => (
<div key={litter.id} className="card"> <div
<h3>{litter.sire_name} × {litter.dam_name}</h3> key={litter.id}
<p style={{ color: 'var(--text-secondary)', marginTop: '0.5rem' }}> className="card"
Breeding Date: {new Date(litter.breeding_date).toLocaleDateString()} style={{ cursor: 'pointer', transition: 'border-color 0.2s' }}
</p> onClick={() => navigate(`/litters/${litter.id}`)}
{litter.whelping_date && ( >
<p style={{ color: 'var(--text-secondary)' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
Whelping Date: {new Date(litter.whelping_date).toLocaleDateString()} <div style={{ flex: 1 }}>
</p> <h3 style={{ marginBottom: '0.5rem' }}>
)} 🐾 {litter.sire_name} × {litter.dam_name}
<p style={{ marginTop: '0.5rem' }}> </h3>
<strong>Puppies:</strong> {litter.puppy_count || litter.puppies?.length || 0} <div style={{ display: 'flex', gap: '1.5rem', flexWrap: 'wrap', color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
</p> <span>📅 Bred: {new Date(litter.breeding_date).toLocaleDateString()}</span>
{litter.whelping_date && (
<span>🐕 Whelped: {new Date(litter.whelping_date).toLocaleDateString()}</span>
)}
<span style={{ color: 'var(--accent)', fontWeight: 600 }}>
{litter.actual_puppy_count ?? litter.puppies?.length ?? litter.puppy_count ?? 0} puppies
</span>
</div>
{litter.notes && (
<p style={{ marginTop: '0.5rem', fontSize: '0.85rem', color: 'var(--text-secondary)', fontStyle: 'italic' }}>
{litter.notes}
</p>
)}
</div>
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
<button
className="btn-icon"
title="Edit litter"
onClick={(e) => handleEdit(e, litter)}
>
<Edit2 size={16} />
</button>
<button
className="btn-icon"
title="Delete litter"
onClick={(e) => handleDelete(e, litter.id)}
style={{ color: '#e53e3e' }}
>
<Trash2 size={16} />
</button>
<ChevronRight size={20} style={{ color: 'var(--text-secondary)' }} />
</div>
</div>
</div> </div>
))} ))}
</div> </div>
)} )}
{showForm && (
<LitterForm
litter={editingLitter}
prefill={prefill}
onClose={() => setShowForm(false)}
onSave={handleSave}
/>
)}
</div> </div>
) )
} }