Add Dashboard page
This commit is contained in:
113
client/src/pages/Dashboard.jsx
Normal file
113
client/src/pages/Dashboard.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Dog, Activity, Heart, AlertCircle } from 'lucide-react'
|
||||
import axios from 'axios'
|
||||
|
||||
function Dashboard() {
|
||||
const [stats, setStats] = useState({
|
||||
totalDogs: 0,
|
||||
males: 0,
|
||||
females: 0,
|
||||
totalLitters: 0,
|
||||
activeHeatCycles: 0
|
||||
})
|
||||
const [recentDogs, setRecentDogs] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
fetchDashboardData()
|
||||
}, [])
|
||||
|
||||
const fetchDashboardData = async () => {
|
||||
try {
|
||||
const [dogsRes, littersRes, heatCyclesRes] = await Promise.all([
|
||||
axios.get('/api/dogs'),
|
||||
axios.get('/api/litters'),
|
||||
axios.get('/api/breeding/heat-cycles/active')
|
||||
])
|
||||
|
||||
const dogs = 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,
|
||||
activeHeatCycles: heatCyclesRes.data.length
|
||||
})
|
||||
|
||||
setRecentDogs(dogs.slice(0, 6))
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
console.error('Error fetching dashboard data:', error)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <div className="container loading">Loading dashboard...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<h1 style={{ marginBottom: '2rem' }}>Dashboard</h1>
|
||||
|
||||
<div className="grid grid-3" style={{ marginBottom: '3rem' }}>
|
||||
<div className="card" style={{ textAlign: 'center' }}>
|
||||
<Dog size={48} style={{ color: 'var(--primary)', margin: '0 auto 1rem' }} />
|
||||
<h3 style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>{stats.totalDogs}</h3>
|
||||
<p style={{ color: 'var(--text-secondary)' }}>Total Dogs</p>
|
||||
<p style={{ fontSize: '0.875rem', marginTop: '0.5rem' }}>
|
||||
{stats.males} Males • {stats.females} Females
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ textAlign: 'center' }}>
|
||||
<Activity size={48} style={{ color: 'var(--success)', margin: '0 auto 1rem' }} />
|
||||
<h3 style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>{stats.totalLitters}</h3>
|
||||
<p style={{ color: 'var(--text-secondary)' }}>Total Litters</p>
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ textAlign: 'center' }}>
|
||||
<Heart size={48} style={{ color: 'var(--danger)', margin: '0 auto 1rem' }} />
|
||||
<h3 style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>{stats.activeHeatCycles}</h3>
|
||||
<p style={{ color: 'var(--text-secondary)' }}>Active Heat Cycles</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
|
||||
<h2>Recent Dogs</h2>
|
||||
<Link to="/dogs" className="btn btn-primary">View All</Link>
|
||||
</div>
|
||||
|
||||
{recentDogs.length === 0 ? (
|
||||
<div className="card" style={{ textAlign: 'center', padding: '3rem' }}>
|
||||
<AlertCircle size={48} style={{ color: 'var(--text-secondary)', margin: '0 auto 1rem' }} />
|
||||
<h3>No dogs registered yet</h3>
|
||||
<p style={{ color: 'var(--text-secondary)', marginBottom: '1.5rem' }}>Start by adding your first dog to the system</p>
|
||||
<Link to="/dogs" className="btn btn-primary">Add Dog</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-3">
|
||||
{recentDogs.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}</p>
|
||||
{dog.registration_number && (
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '0.75rem', marginTop: '0.25rem' }}>{dog.registration_number}</p>
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
Reference in New Issue
Block a user