From 4d5bdaad570870feb81fe74b26fbb24d7f20b85e Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Mon, 21 Aug 2023 17:53:51 +0200 Subject: [PATCH] Build tree couples --- geneit_app/src/api/CoupleApi.ts | 9 +++ .../routes/family/FamilyMemberTreeRoute.tsx | 24 ++++-- .../src/routes/family/FamilyTreeRoute.tsx | 2 +- geneit_app/src/utils/family_tree.ts | 79 +++++++++++++++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 geneit_app/src/utils/family_tree.ts diff --git a/geneit_app/src/api/CoupleApi.ts b/geneit_app/src/api/CoupleApi.ts index 2a65e67..4944c98 100644 --- a/geneit_app/src/api/CoupleApi.ts +++ b/geneit_app/src/api/CoupleApi.ts @@ -1,5 +1,6 @@ import { APIClient } from "./ApiClient"; import { DateValue, Member } from "./MemberApi"; +import { ServerApi } from "./ServerApi"; interface CoupleApiInterface { id: number; @@ -99,6 +100,10 @@ export class Couple implements CoupleApiInterface { day: this.divorce_day, }; } + + otherPersonID(id: number): number | undefined { + return id === this.wife ? this.husband : this.wife; + } } export class CouplesList { @@ -137,6 +142,10 @@ export class CouplesList { getAllOf(m: Member): Couple[] { return this.filter((c) => c.husband === m.id || c.wife === m.id); } + + getFirstMariedOf(m: Member): Couple | undefined { + return this.getAllOf(m).find((c) => (c.state = "M")); + } } export class CoupleApi { diff --git a/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx b/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx index eab8ab0..759ef8a 100644 --- a/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx +++ b/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx @@ -1,5 +1,4 @@ -import { useParams } from "react-router-dom"; -import { useFamily } from "../../widgets/BaseFamilyRoute"; +import ClearIcon from "@mui/icons-material/Clear"; import { Alert, FormControl, @@ -12,10 +11,15 @@ import { Tab, Tabs, } from "@mui/material"; -import { MemberItem } from "../../widgets/MemberItem"; -import ClearIcon from "@mui/icons-material/Clear"; -import { RouterLink } from "../../widgets/RouterLink"; import React from "react"; +import { useParams } from "react-router-dom"; +import { + buildAscendingTree, + buildDescendingTree, +} from "../../utils/family_tree"; +import { useFamily } from "../../widgets/BaseFamilyRoute"; +import { MemberItem } from "../../widgets/MemberItem"; +import { RouterLink } from "../../widgets/RouterLink"; enum CurrTab { BasicTree, @@ -37,6 +41,16 @@ export function FamilyMemberTreeRoute(): React.ReactElement { const member = family.members.get(Number(memberId)); + const tree = React.useMemo( + () => + !member + ? null + : currMode === TreeMode.Ascending + ? buildAscendingTree(member.id, family.members, family.couples) + : buildDescendingTree(member.id, family.members, family.couples), + [member, currMode, family.members, family.couples] + ); + if (!member) { return ( diff --git a/geneit_app/src/routes/family/FamilyTreeRoute.tsx b/geneit_app/src/routes/family/FamilyTreeRoute.tsx index 26c5e7f..7ca9c75 100644 --- a/geneit_app/src/routes/family/FamilyTreeRoute.tsx +++ b/geneit_app/src/routes/family/FamilyTreeRoute.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; -import { MemberInput } from "../../widgets/forms/MemberInput"; import { useFamily } from "../../widgets/BaseFamilyRoute"; +import { MemberInput } from "../../widgets/forms/MemberInput"; export function FamilyTreeRoute(): React.ReactElement { const n = useNavigate(); diff --git a/geneit_app/src/utils/family_tree.ts b/geneit_app/src/utils/family_tree.ts new file mode 100644 index 0000000..ec53aa1 --- /dev/null +++ b/geneit_app/src/utils/family_tree.ts @@ -0,0 +1,79 @@ +import { Couple, CouplesList } from "../api/CoupleApi"; +import { Member, MembersList } from "../api/MemberApi"; + +export interface CoupleInformation { + couple: Couple; + member: Member; + down: FamilyTreeNode[]; +} + +export interface FamilyTreeNode { + down?: FamilyTreeNode[]; + member: Member; + couples?: CoupleInformation[]; +} + +export function buildAscendingTree( + memberId: number, + members: MembersList, + couples: CouplesList +): FamilyTreeNode { + const member = members.get(memberId)!; + + const parents = []; + if (member.father) + parents.push(buildAscendingTree(member.father, members, couples)); + + if (member.mother) + parents.push(buildAscendingTree(member.mother, members, couples)); + + return { + member: member, + down: parents, + }; +} + +export function buildDescendingTree( + memberId: number, + members: MembersList, + couples: CouplesList +): FamilyTreeNode { + const member = members.get(memberId)!; + + const children = members.children(member.id); + + // Process user couples + const member_couples: CoupleInformation[] = []; + for (const c of couples.getAllOf(member)) { + // Currently, couples with missing pair information are not supported + const pairId = c.otherPersonID(member.id); + if (!pairId) continue; + const pair = members.get(pairId); + + const c_children: Member[] = []; + + // Determine children associated with each couple + for (let index = 0; index < children.length; ) { + const c = children[index]; + if (c.mother !== pairId && c.father !== pairId) { + index++; + continue; + } + + children.splice(index, 1); + c_children.push(c); + } + + member_couples.push({ + couple: c, + member: pair!, + down: c_children.map((c) => buildDescendingTree(c.id, members, couples)), + }); + } + + return { + member: member, + down: children.map((c) => buildDescendingTree(c.id, members, couples)), + couples: member_couples, + }; +}