From 42bab14ac3a8dd65ee79ed07937148382f583a96 Mon Sep 17 00:00:00 2001 From: jason Date: Thu, 12 Mar 2026 07:27:41 -0500 Subject: [PATCH] reverse pedigree --- client/src/pages/PedigreeView.jsx | 166 +++++++++++++++++----------- client/src/utils/pedigreeHelpers.js | 44 ++++++++ 2 files changed, 143 insertions(+), 67 deletions(-) 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