feat: rebuild LitterList with create/edit/delete and LitterForm integration
This commit is contained in:
@@ -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}`)}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<h3 style={{ marginBottom: '0.5rem' }}>
|
||||||
|
🐾 {litter.sire_name} × {litter.dam_name}
|
||||||
|
</h3>
|
||||||
|
<div style={{ display: 'flex', gap: '1.5rem', flexWrap: 'wrap', color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
|
||||||
|
<span>📅 Bred: {new Date(litter.breeding_date).toLocaleDateString()}</span>
|
||||||
{litter.whelping_date && (
|
{litter.whelping_date && (
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
<span>🐕 Whelped: {new Date(litter.whelping_date).toLocaleDateString()}</span>
|
||||||
Whelping Date: {new Date(litter.whelping_date).toLocaleDateString()}
|
)}
|
||||||
|
<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>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p style={{ marginTop: '0.5rem' }}>
|
</div>
|
||||||
<strong>Puppies:</strong> {litter.puppy_count || litter.puppies?.length || 0}
|
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||||
</p>
|
<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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user