Add DogList page
This commit is contained in:
118
client/src/pages/DogList.jsx
Normal file
118
client/src/pages/DogList.jsx
Normal 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
|
||||
Reference in New Issue
Block a user