Rebuild PedigreeView with interactive tree visualization
This commit is contained in:
@@ -1,16 +1,210 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
import { useEffect, useState } from 'react'
|
||||||
import { GitBranch } from 'lucide-react'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
|
import { ArrowLeft, GitBranch, AlertCircle, Loader } from 'lucide-react'
|
||||||
|
import axios from 'axios'
|
||||||
|
import PedigreeTree from '../components/PedigreeTree'
|
||||||
|
import { transformPedigreeData, formatCOI, getPedigreeCompleteness } from '../utils/pedigreeHelpers'
|
||||||
|
|
||||||
function PedigreeView() {
|
function PedigreeView() {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
const [dog, setDog] = useState(null)
|
||||||
|
const [pedigreeData, setPedigreeData] = useState(null)
|
||||||
|
const [coiData, setCoiData] = useState(null)
|
||||||
|
const [generations, setGenerations] = useState(5)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPedigreeData()
|
||||||
|
}, [id, generations])
|
||||||
|
|
||||||
|
const fetchPedigreeData = async () => {
|
||||||
|
setLoading(true)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch pedigree tree data
|
||||||
|
const pedigreeRes = await axios.get(`/api/pedigree/${id}`)
|
||||||
|
const dogData = pedigreeRes.data
|
||||||
|
|
||||||
|
setDog(dogData)
|
||||||
|
|
||||||
|
// Transform data for react-d3-tree
|
||||||
|
const treeData = transformPedigreeData(dogData, generations)
|
||||||
|
setPedigreeData(treeData)
|
||||||
|
|
||||||
|
// Fetch COI calculation
|
||||||
|
try {
|
||||||
|
const coiRes = await axios.get(`/api/pedigree/${id}/coi`)
|
||||||
|
setCoiData(coiRes.data)
|
||||||
|
} catch (coiError) {
|
||||||
|
console.warn('COI calculation unavailable:', coiError)
|
||||||
|
setCoiData(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching pedigree:', err)
|
||||||
|
setError(err.response?.data?.error || 'Failed to load pedigree data')
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const completeness = pedigreeData ? getPedigreeCompleteness(pedigreeData, generations) : 0
|
||||||
|
const coiInfo = formatCOI(coiData?.coi)
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className="loading" style={{ textAlign: 'center', padding: '4rem' }}>
|
||||||
|
<Loader size={48} style={{ animation: 'spin 1s linear infinite', margin: '0 auto 1rem' }} />
|
||||||
|
<p>Loading pedigree data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className="card" style={{ textAlign: 'center', padding: '3rem' }}>
|
||||||
|
<AlertCircle size={64} style={{ color: 'var(--danger)', margin: '0 auto 1rem' }} />
|
||||||
|
<h2>Error Loading Pedigree</h2>
|
||||||
|
<p style={{ color: 'var(--text-secondary)', marginTop: '0.5rem' }}>{error}</p>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => navigate('/dogs')}
|
||||||
|
style={{ marginTop: '1.5rem' }}
|
||||||
|
>
|
||||||
|
Back to Dogs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<h1 style={{ marginBottom: '2rem' }}>Pedigree Chart</h1>
|
{/* Header */}
|
||||||
<div className="card" style={{ textAlign: 'center', padding: '4rem' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '1.5rem' }}>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={() => navigate(`/dogs/${id}`)}
|
||||||
|
style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}
|
||||||
|
>
|
||||||
|
<ArrowLeft size={20} />
|
||||||
|
Back to Profile
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<h1 style={{ margin: 0, display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||||
|
<GitBranch size={32} />
|
||||||
|
{dog?.name}'s Pedigree
|
||||||
|
</h1>
|
||||||
|
{dog?.registration_number && (
|
||||||
|
<p style={{ color: 'var(--text-secondary)', margin: '0.25rem 0 0 0' }}>
|
||||||
|
Registration: {dog.registration_number}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats Bar */}
|
||||||
|
<div className="card" style={{ marginBottom: '1rem', padding: '1rem' }}>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1.5rem' }}>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: '0.875rem', color: 'var(--text-secondary)', marginBottom: '0.25rem' }}>
|
||||||
|
Coefficient of Inbreeding
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||||
|
<span style={{ fontSize: '1.5rem', fontWeight: '700', color: coiInfo.color }}>
|
||||||
|
{coiInfo.value}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
padding: '0.25rem 0.5rem',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: coiInfo.color + '20',
|
||||||
|
color: coiInfo.color,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
fontWeight: '600'
|
||||||
|
}}>
|
||||||
|
{coiInfo.level}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginTop: '0.25rem' }}>
|
||||||
|
{coiInfo.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: '0.875rem', color: 'var(--text-secondary)', marginBottom: '0.25rem' }}>
|
||||||
|
Pedigree Completeness
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '1.5rem', fontWeight: '700' }}>
|
||||||
|
{completeness}%
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: '0.5rem' }}>
|
||||||
|
<div style={{
|
||||||
|
height: '8px',
|
||||||
|
background: '#e5e7eb',
|
||||||
|
borderRadius: '4px',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
height: '100%',
|
||||||
|
width: `${completeness}%`,
|
||||||
|
background: completeness === 100 ? '#10b981' : '#3b82f6',
|
||||||
|
transition: 'width 0.3s ease'
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: '0.875rem', color: 'var(--text-secondary)', marginBottom: '0.25rem' }}>
|
||||||
|
Generations Displayed
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className="input"
|
||||||
|
value={generations}
|
||||||
|
onChange={(e) => setGenerations(Number(e.target.value))}
|
||||||
|
style={{ marginTop: '0.25rem' }}
|
||||||
|
>
|
||||||
|
<option value={3}>3 Generations</option>
|
||||||
|
<option value={4}>4 Generations</option>
|
||||||
|
<option value={5}>5 Generations</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pedigree Tree */}
|
||||||
|
<div className="card" style={{ padding: 0 }}>
|
||||||
|
{pedigreeData ? (
|
||||||
|
<PedigreeTree
|
||||||
|
dogId={id}
|
||||||
|
pedigreeData={pedigreeData}
|
||||||
|
coi={coiData?.coi}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ textAlign: 'center', padding: '4rem' }}>
|
||||||
<GitBranch size={64} style={{ color: 'var(--text-secondary)', margin: '0 auto 1rem' }} />
|
<GitBranch size={64} style={{ color: 'var(--text-secondary)', margin: '0 auto 1rem' }} />
|
||||||
<h2>Interactive Pedigree Visualization</h2>
|
<h3>No Pedigree Data Available</h3>
|
||||||
<p style={{ color: 'var(--text-secondary)', marginTop: '0.5rem' }}>Coming soon - React D3 Tree integration for dog ID: {id}</p>
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
Add parent information to this dog to build the pedigree tree.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Help Text */}
|
||||||
|
<div className="card" style={{ marginTop: '1rem', background: '#eff6ff', border: '1px solid #bfdbfe' }}>
|
||||||
|
<div style={{ fontSize: '0.875rem', color: '#1e40af' }}>
|
||||||
|
<strong>💡 Tip:</strong> Click on any ancestor node to navigate to their profile.
|
||||||
|
Use the zoom controls to explore the tree, or drag to pan around.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user