Add DogList page

This commit is contained in:
2026-03-08 22:53:37 -05:00
parent 450c18e58f
commit 36bcdb1095

View File

@@ -0,0 +1,118 @@
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { Dog, Plus, Search } from 'lucide-react'
import axios from 'axios'
function DogList() {
const [dogs, setDogs] = useState([])
const [filteredDogs, setFilteredDogs] = useState([])
const [search, setSearch] = useState('')
const [sexFilter, setSexFilter] = useState('all')
const [loading, setLoading] = useState(true)
const [showAddModal, setShowAddModal] = useState(false)
useEffect(() => {
fetchDogs()
}, [])
useEffect(() => {
filterDogs()
}, [dogs, search, sexFilter])
const fetchDogs = async () => {
try {
const res = await axios.get('/api/dogs')
setDogs(res.data)
setLoading(false)
} catch (error) {
console.error('Error fetching dogs:', error)
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)
}
if (loading) {
return <div className="container loading">Loading dogs...</div>
}
return (
<div className="container">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
<h1>Dogs</h1>
<button className="btn btn-primary" onClick={() => setShowAddModal(true)}>
<Plus size={20} />
Add Dog
</button>
</div>
<div className="card" style={{ marginBottom: '2rem' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: '1rem' }}>
<div style={{ position: 'relative' }}>
<Search size={20} style={{ position: 'absolute', left: '0.75rem', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-secondary)' }} />
<input
type="text"
className="input"
placeholder="Search by name or registration number..."
value={search}
onChange={(e) => setSearch(e.target.value)}
style={{ paddingLeft: '2.5rem' }}
/>
</div>
<select className="input" value={sexFilter} onChange={(e) => setSexFilter(e.target.value)} style={{ width: 'auto' }}>
<option value="all">All</option>
<option value="male">Males</option>
<option value="female">Females</option>
</select>
</div>
</div>
<div className="grid grid-3">
{filteredDogs.map(dog => (
<Link key={dog.id} to={`/dogs/${dog.id}`} className="card" style={{ textDecoration: 'none', color: 'inherit' }}>
<div style={{ aspectRatio: '1', background: 'var(--bg-secondary)', borderRadius: '0.375rem', marginBottom: '1rem', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{dog.photo_urls && dog.photo_urls.length > 0 ? (
<img src={dog.photo_urls[0]} alt={dog.name} style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: '0.375rem' }} />
) : (
<Dog size={48} style={{ color: 'var(--text-secondary)' }} />
)}
</div>
<h3>{dog.name}</h3>
<p style={{ color: 'var(--text-secondary)', fontSize: '0.875rem' }}>
{dog.breed} {dog.sex === 'male' ? '♂' : '♀'}
</p>
{dog.registration_number && (
<p style={{ color: 'var(--text-secondary)', fontSize: '0.75rem', marginTop: '0.25rem' }}>{dog.registration_number}</p>
)}
{dog.birth_date && (
<p style={{ color: 'var(--text-secondary)', fontSize: '0.75rem' }}>Born: {new Date(dog.birth_date).toLocaleDateString()}</p>
)}
</Link>
))}
</div>
{filteredDogs.length === 0 && (
<div className="card" style={{ textAlign: 'center', padding: '3rem' }}>
<p style={{ color: 'var(--text-secondary)' }}>No dogs found matching your search criteria.</p>
</div>
)}
</div>
)
}
export default DogList