diff --git a/client/src/pages/PedigreeView.jsx b/client/src/pages/PedigreeView.jsx
index 0d72a9a..89757fe 100644
--- a/client/src/pages/PedigreeView.jsx
+++ b/client/src/pages/PedigreeView.jsx
@@ -3,7 +3,7 @@ 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'
+import { transformPedigreeData, transformDescendantData, formatCOI, getPedigreeCompleteness } from '../utils/pedigreeHelpers'
function PedigreeView() {
const { id } = useParams()
@@ -14,28 +14,39 @@ function PedigreeView() {
const [pedigreeData, setPedigreeData] = useState(null)
const [coiData, setCoiData] = useState(null)
const [generations, setGenerations] = useState(5)
+ const [viewMode, setViewMode] = useState('ancestors')
useEffect(() => {
fetchPedigreeData()
- }, [id, generations])
+ }, [id, generations, viewMode])
const fetchPedigreeData = async () => {
setLoading(true)
setError('')
try {
- const pedigreeRes = await axios.get(`/api/pedigree/${id}`)
- const dogData = pedigreeRes.data
- setDog(dogData)
+ if (viewMode === 'ancestors') {
+ const pedigreeRes = await axios.get(`/api/pedigree/${id}`)
+ const dogData = pedigreeRes.data
+ setDog(dogData)
- const treeData = transformPedigreeData(dogData, generations)
- setPedigreeData(treeData)
+ const treeData = transformPedigreeData(dogData, generations)
+ setPedigreeData(treeData)
- try {
- const coiRes = await axios.get(`/api/pedigree/${id}/coi`)
- setCoiData(coiRes.data)
- } catch (coiError) {
- console.warn('COI calculation unavailable:', coiError)
+ try {
+ const coiRes = await axios.get(`/api/pedigree/${id}/coi`)
+ setCoiData(coiRes.data)
+ } catch (coiError) {
+ console.warn('COI calculation unavailable:', coiError)
+ setCoiData(null)
+ }
+ } else {
+ const descendantRes = await axios.get(`/api/pedigree/${id}/descendants?generations=${generations}`)
+ const dogData = descendantRes.data
+ setDog(dogData)
+
+ const treeData = transformDescendantData(dogData, generations)
+ setPedigreeData(treeData)
setCoiData(null)
}
@@ -86,7 +97,7 @@ function PedigreeView() {
return (
{/* Header */}
-
+
-
+
- {dog?.name}'s Pedigree
+ {dog?.name}'s {viewMode === 'ancestors' ? 'Pedigree' : 'Descendants'}
{dog?.registration_number && (
@@ -107,65 +118,86 @@ function PedigreeView() {
)}
+
+
+
+
+
{/* Stats Bar */}
- {/* COI */}
-
-
- Coefficient of Inbreeding
-
-
-
- {coiInfo.value}
-
-
- {coiInfo.level}
-
-
-
- {coiInfo.description}
-
-
-
- {/* Completeness */}
-
-
- Pedigree Completeness
-
-
- {completeness}%
-
-
-
-
+ {viewMode === 'ancestors' && (
+ <>
+ {/* COI */}
+
+
+ Coefficient of Inbreeding
+
+
+
+ {coiInfo.value}
+
+
+ {coiInfo.level}
+
+
+
+ {coiInfo.description}
+
-
-
+
+ {/* Completeness */}
+
+
+ Pedigree Completeness
+
+
+ {completeness}%
+
+
+
+ >
+ )}
{/* Generations */}
diff --git a/client/src/utils/pedigreeHelpers.js b/client/src/utils/pedigreeHelpers.js
index 7229a3d..52171f1 100644
--- a/client/src/utils/pedigreeHelpers.js
+++ b/client/src/utils/pedigreeHelpers.js
@@ -180,4 +180,48 @@ export const getPedigreeCompleteness = (treeData, targetGenerations = 5) => {
const expectedTotal = Math.pow(2, targetGenerations) - 1
const actualCount = countAncestors(treeData)
return Math.min(100, Math.round((actualCount / expectedTotal) * 100))
+}
+
+/**
+ * Transform API descendant data to react-d3-tree format
+ * @param {Object} dog - Dog object from API with nested offspring array
+ * @param {number} maxGenerations - Maximum generations to display (default 3)
+ * @returns {Object} Tree data in react-d3-tree format
+ */
+export const transformDescendantData = (dog, maxGenerations = 3) => {
+ if (!dog) return null
+
+ const buildTree = (dogData, generation = 0) => {
+ if (!dogData || generation >= maxGenerations) {
+ return null
+ }
+
+ const node = {
+ name: dogData.name || 'Unknown',
+ attributes: {
+ id: dogData.id,
+ sex: dogData.sex,
+ registration: dogData.registration_number || '',
+ birth_year: dogData.birth_date ? new Date(dogData.birth_date).getFullYear() : ''
+ },
+ children: []
+ }
+
+ if (dogData.offspring && dogData.offspring.length > 0) {
+ dogData.offspring.forEach(child => {
+ const childNode = buildTree(child, generation + 1)
+ if (childNode) {
+ node.children.push(childNode)
+ }
+ })
+ }
+
+ if (node.children.length === 0) {
+ delete node.children
+ }
+
+ return node
+ }
+
+ return buildTree(dog)
}
\ No newline at end of file