diff --git a/client/src/components/DogForm.jsx b/client/src/components/DogForm.jsx
index 3eac5be..22bdb83 100644
--- a/client/src/components/DogForm.jsx
+++ b/client/src/components/DogForm.jsx
@@ -64,8 +64,8 @@ function DogForm({ dog, onClose, onSave, isExternal = false }) {
const fetchLitters = async () => {
try {
- const res = await axios.get('/api/litters')
- const data = res.data || []
+ const res = await axios.get('/api/litters', { params: { limit: 200 } })
+ const data = res.data.data || []
setLitters(data)
setLittersAvailable(data.length > 0)
if (data.length === 0) setUseManualParents(true)
diff --git a/client/src/components/LitterForm.jsx b/client/src/components/LitterForm.jsx
index ca921bd..7576dd8 100644
--- a/client/src/components/LitterForm.jsx
+++ b/client/src/components/LitterForm.jsx
@@ -39,7 +39,7 @@ function LitterForm({ litter, prefill, onClose, onSave }) {
const fetchDogs = async () => {
try {
- const res = await axios.get('/api/dogs')
+ const res = await axios.get('/api/dogs/all')
setDogs(res.data)
} catch (error) {
console.error('Error fetching dogs:', error)
diff --git a/client/src/pages/Dashboard.jsx b/client/src/pages/Dashboard.jsx
index 6f1da89..e197c6c 100644
--- a/client/src/pages/Dashboard.jsx
+++ b/client/src/pages/Dashboard.jsx
@@ -21,21 +21,21 @@ function Dashboard() {
const fetchDashboardData = async () => {
try {
const [dogsRes, littersRes, heatCyclesRes] = await Promise.all([
- axios.get('/api/dogs'),
- axios.get('/api/litters'),
+ axios.get('/api/dogs', { params: { page: 1, limit: 8 } }),
+ axios.get('/api/litters', { params: { page: 1, limit: 1 } }),
axios.get('/api/breeding/heat-cycles/active')
])
- const dogs = dogsRes.data
+ const { data: recentDogsList, stats: dogStats } = dogsRes.data
setStats({
- totalDogs: dogs.length,
- males: dogs.filter(d => d.sex === 'male').length,
- females: dogs.filter(d => d.sex === 'female').length,
- totalLitters: littersRes.data.length,
+ totalDogs: dogStats?.total ?? 0,
+ males: dogStats?.males ?? 0,
+ females: dogStats?.females ?? 0,
+ totalLitters: littersRes.data.total,
activeHeatCycles: heatCyclesRes.data.length
})
- setRecentDogs(dogs.slice(0, 8))
+ setRecentDogs(recentDogsList)
setLoading(false)
} catch (error) {
console.error('Error fetching dashboard data:', error)
diff --git a/client/src/pages/DogList.jsx b/client/src/pages/DogList.jsx
index d1f67a4..a9910ce 100644
--- a/client/src/pages/DogList.jsx
+++ b/client/src/pages/DogList.jsx
@@ -1,57 +1,69 @@
-import { useEffect, useState } from 'react'
+import { useEffect, useState, useRef } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Dog, Plus, Search, Calendar, Hash, ArrowRight, Trash2 } from 'lucide-react'
import axios from 'axios'
import DogForm from '../components/DogForm'
import { ChampionBadge, ChampionBloodlineBadge } from '../components/ChampionBadge'
+const LIMIT = 50
+
function DogList() {
const [dogs, setDogs] = useState([])
- const [filteredDogs, setFilteredDogs] = useState([])
+ const [total, setTotal] = useState(0)
+ const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const [sexFilter, setSexFilter] = useState('all')
const [loading, setLoading] = useState(true)
const [showAddModal, setShowAddModal] = useState(false)
const [deleteTarget, setDeleteTarget] = useState(null) // { id, name }
const [deleting, setDeleting] = useState(false)
+ const searchTimerRef = useRef(null)
- useEffect(() => { fetchDogs() }, [])
- useEffect(() => { filterDogs() }, [dogs, search, sexFilter])
+ useEffect(() => { fetchDogs(1, '', 'all') }, []) // eslint-disable-line
- const fetchDogs = async () => {
+ const fetchDogs = async (p, q, s) => {
+ setLoading(true)
try {
- const res = await axios.get('/api/dogs')
- setDogs(res.data)
- setLoading(false)
+ const params = { page: p, limit: LIMIT }
+ if (q) params.search = q
+ if (s !== 'all') params.sex = s
+ const res = await axios.get('/api/dogs', { params })
+ setDogs(res.data.data)
+ setTotal(res.data.total)
+ setPage(p)
} catch (error) {
console.error('Error fetching dogs:', error)
+ } finally {
setLoading(false)
}
}
- const filterDogs = () => {
- let filtered = dogs
- if (search) {
- filtered = filtered.filter(dog =>
- dog.name.toLowerCase().includes(search.toLowerCase()) ||
- (dog.registration_number && dog.registration_number.toLowerCase().includes(search.toLowerCase()))
- )
- }
- if (sexFilter !== 'all') {
- filtered = filtered.filter(dog => dog.sex === sexFilter)
- }
- setFilteredDogs(filtered)
+ const handleSearchChange = (value) => {
+ setSearch(value)
+ if (searchTimerRef.current) clearTimeout(searchTimerRef.current)
+ searchTimerRef.current = setTimeout(() => fetchDogs(1, value, sexFilter), 300)
}
- const handleSave = () => { fetchDogs() }
+ const handleSexChange = (value) => {
+ setSexFilter(value)
+ fetchDogs(1, search, value)
+ }
+
+ const handleClearFilters = () => {
+ setSearch('')
+ setSexFilter('all')
+ fetchDogs(1, '', 'all')
+ }
+
+ const handleSave = () => { fetchDogs(page, search, sexFilter) }
const handleDelete = async () => {
if (!deleteTarget) return
setDeleting(true)
try {
await axios.delete(`/api/dogs/${deleteTarget.id}`)
- setDogs(prev => prev.filter(d => d.id !== deleteTarget.id))
setDeleteTarget(null)
+ fetchDogs(page, search, sexFilter)
} catch (err) {
console.error('Delete failed:', err)
alert('Failed to delete dog. Please try again.')
@@ -60,6 +72,8 @@ function DogList() {
}
}
+ const totalPages = Math.ceil(total / LIMIT)
+
const calculateAge = (birthDate) => {
if (!birthDate) return null
const today = new Date()
@@ -85,7 +99,7 @@ function DogList() {
Dogs
- {filteredDogs.length} {filteredDogs.length === 1 ? 'dog' : 'dogs'}
+ {total} {total === 1 ? 'dog' : 'dogs'}
{search || sexFilter !== 'all' ? ' matching filters' : ' total'}
@@ -105,11 +119,11 @@ function DogList() {
className="input"
placeholder="Search by name or registration..."
value={search}
- onChange={(e) => setSearch(e.target.value)}
+ onChange={(e) => handleSearchChange(e.target.value)}
style={{ paddingLeft: '2.75rem' }}
/>
-