All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Start our journey into turning GeneIT as afully featured family intranet by making genealogy a feature that can be disabled by family admins Reviewed-on: #175
		
			
				
	
	
		
			360 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { APIClient } from "../ApiClient";
 | 
						|
import { Couple } from "./CoupleApi";
 | 
						|
 | 
						|
export type Sex = "M" | "F";
 | 
						|
 | 
						|
export interface MemberDataApi {
 | 
						|
  id: number;
 | 
						|
  family_id: number;
 | 
						|
  first_name?: string;
 | 
						|
  last_name?: string;
 | 
						|
  birth_last_name?: string;
 | 
						|
  photo_id?: number;
 | 
						|
  signed_photo_id?: number;
 | 
						|
  email?: string;
 | 
						|
  phone?: string;
 | 
						|
  address?: string;
 | 
						|
  city?: string;
 | 
						|
  postal_code?: string;
 | 
						|
  country?: string;
 | 
						|
  sex?: Sex;
 | 
						|
  time_create?: number;
 | 
						|
  time_update?: number;
 | 
						|
  mother?: number;
 | 
						|
  father?: number;
 | 
						|
  birth_year?: number;
 | 
						|
  birth_month?: number;
 | 
						|
  birth_day?: number;
 | 
						|
  dead: boolean;
 | 
						|
  death_year?: number;
 | 
						|
  death_month?: number;
 | 
						|
  death_day?: number;
 | 
						|
  note?: string;
 | 
						|
}
 | 
						|
 | 
						|
export interface DateValue {
 | 
						|
  year?: number;
 | 
						|
  month?: number;
 | 
						|
  day?: number;
 | 
						|
}
 | 
						|
 | 
						|
export class Member implements MemberDataApi {
 | 
						|
  id: number;
 | 
						|
  family_id: number;
 | 
						|
  first_name?: string;
 | 
						|
  last_name?: string;
 | 
						|
  birth_last_name?: string;
 | 
						|
  photo_id?: number;
 | 
						|
  signed_photo_id?: number;
 | 
						|
  email?: string;
 | 
						|
  phone?: string;
 | 
						|
  address?: string;
 | 
						|
  city?: string;
 | 
						|
  postal_code?: string;
 | 
						|
  country?: string;
 | 
						|
  sex?: Sex;
 | 
						|
  time_create?: number;
 | 
						|
  time_update?: number;
 | 
						|
  mother?: number;
 | 
						|
  father?: number;
 | 
						|
  birth_year?: number;
 | 
						|
  birth_month?: number;
 | 
						|
  birth_day?: number;
 | 
						|
  dead!: boolean;
 | 
						|
  death_year?: number;
 | 
						|
  death_month?: number;
 | 
						|
  death_day?: number;
 | 
						|
  note?: string;
 | 
						|
 | 
						|
  constructor(m: MemberDataApi) {
 | 
						|
    this.id = m.id;
 | 
						|
    this.family_id = m.family_id;
 | 
						|
    this.first_name = m.first_name;
 | 
						|
    this.last_name = m.last_name;
 | 
						|
    this.birth_last_name = m.birth_last_name;
 | 
						|
    this.photo_id = m.photo_id;
 | 
						|
    this.signed_photo_id = m.signed_photo_id;
 | 
						|
    this.email = m.email;
 | 
						|
    this.phone = m.phone;
 | 
						|
    this.address = m.address;
 | 
						|
    this.city = m.city;
 | 
						|
    this.postal_code = m.postal_code;
 | 
						|
    this.country = m.country;
 | 
						|
    this.sex = m.sex;
 | 
						|
    this.time_create = m.time_create;
 | 
						|
    this.time_update = m.time_update;
 | 
						|
    this.mother = m.mother;
 | 
						|
    this.father = m.father;
 | 
						|
    this.birth_year = m.birth_year;
 | 
						|
    this.birth_month = m.birth_month;
 | 
						|
    this.birth_day = m.birth_day;
 | 
						|
    this.dead = m.dead;
 | 
						|
    this.death_year = m.death_year;
 | 
						|
    this.death_month = m.death_month;
 | 
						|
    this.death_day = m.death_day;
 | 
						|
    this.note = m.note;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Create an empty member object
 | 
						|
   */
 | 
						|
  static New(family_id: number): Member {
 | 
						|
    return new Member({
 | 
						|
      id: 0,
 | 
						|
      dead: false,
 | 
						|
      family_id: family_id,
 | 
						|
      sex: "M",
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  get lastNameUpperCase(): string | undefined {
 | 
						|
    return this.last_name?.toUpperCase();
 | 
						|
  }
 | 
						|
 | 
						|
  get fullName(): string {
 | 
						|
    const firstName = this.first_name ?? "";
 | 
						|
 | 
						|
    return firstName.length === 0
 | 
						|
      ? this.last_name ?? ""
 | 
						|
      : `${firstName} ${this.last_name?.toUpperCase() ?? ""}`;
 | 
						|
  }
 | 
						|
 | 
						|
  get invertedFullName(): string {
 | 
						|
    const lastName = this.last_name ?? "";
 | 
						|
 | 
						|
    return lastName.length === 0
 | 
						|
      ? this.last_name ?? ""
 | 
						|
      : `${lastName} ${this.first_name ?? ""}`;
 | 
						|
  }
 | 
						|
 | 
						|
  get hasPhoto(): boolean {
 | 
						|
    return this.photo_id !== null;
 | 
						|
  }
 | 
						|
 | 
						|
  get photoURL(): string | null {
 | 
						|
    if (!this.signed_photo_id) return null;
 | 
						|
    return `${APIClient.backendURL()}/photo/${this.signed_photo_id}`;
 | 
						|
  }
 | 
						|
 | 
						|
  get thumbnailURL(): string | null {
 | 
						|
    if (!this.signed_photo_id) return null;
 | 
						|
    return `${APIClient.backendURL()}/photo/${this.signed_photo_id}/thumbnail`;
 | 
						|
  }
 | 
						|
 | 
						|
  get dateOfBirth(): DateValue | undefined {
 | 
						|
    if (!this.birth_day && !this.birth_month && !this.birth_year)
 | 
						|
      return undefined;
 | 
						|
 | 
						|
    return {
 | 
						|
      year: this.birth_year,
 | 
						|
      month: this.birth_month,
 | 
						|
      day: this.birth_day,
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  get dateOfDeath(): DateValue | undefined {
 | 
						|
    if (!this.death_day && !this.death_month && !this.death_year)
 | 
						|
      return undefined;
 | 
						|
 | 
						|
    return {
 | 
						|
      year: this.death_year,
 | 
						|
      month: this.death_month,
 | 
						|
      day: this.death_day,
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  get displayBirthDeath(): string {
 | 
						|
    let birthDeath = [];
 | 
						|
    if (this.dateOfBirth) birthDeath.push(fmtDate(this.dateOfBirth));
 | 
						|
    if (this.dateOfDeath) birthDeath.push(fmtDate(this.dateOfDeath));
 | 
						|
    return birthDeath.join(" - ");
 | 
						|
  }
 | 
						|
 | 
						|
  get displayBirthDeathShort(): string {
 | 
						|
    let birthDeath = [];
 | 
						|
    if (this.birth_year) birthDeath.push(this.birth_year.toString());
 | 
						|
    if (this.death_year) birthDeath.push(this.death_year.toString());
 | 
						|
    return birthDeath.join(" - ");
 | 
						|
  }
 | 
						|
 | 
						|
  get hasContactInfo(): boolean {
 | 
						|
    return this.email ||
 | 
						|
      this.phone ||
 | 
						|
      this.address ||
 | 
						|
      this.city ||
 | 
						|
      this.postal_code ||
 | 
						|
      this.country
 | 
						|
      ? true
 | 
						|
      : false;
 | 
						|
  }
 | 
						|
 | 
						|
  get hasNote(): boolean {
 | 
						|
    return (this.note?.length ?? 0) > 0;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export function fmtDate(d?: DateValue): string {
 | 
						|
  if (d?.year && !d.month && !d.day)
 | 
						|
    return d?.year?.toString().padStart(4, "0");
 | 
						|
 | 
						|
  return `${d?.day?.toString().padStart(2, "0") ?? "__"}/${
 | 
						|
    d?.month?.toString().padStart(2, "0") ?? "__"
 | 
						|
  }/${d?.year?.toString().padStart(4, "0") ?? "__"}`;
 | 
						|
}
 | 
						|
 | 
						|
const OLD_TIME = -58991812735;
 | 
						|
export function dateTimestamp(d?: DateValue): number {
 | 
						|
  if (!d) return OLD_TIME;
 | 
						|
 | 
						|
  const date = new Date();
 | 
						|
  date.setFullYear(d.year ?? 1010, (d.month ?? 1) - 1, d.day ?? 1);
 | 
						|
  return date.getTime() / 1000;
 | 
						|
}
 | 
						|
 | 
						|
export class MembersList {
 | 
						|
  private list: Member[];
 | 
						|
  private map: Map<number, Member>;
 | 
						|
 | 
						|
  constructor(list: Member[]) {
 | 
						|
    this.list = list;
 | 
						|
    this.map = new Map();
 | 
						|
 | 
						|
    for (const m of list) {
 | 
						|
      this.map.set(m.id, m);
 | 
						|
    }
 | 
						|
 | 
						|
    this.list.sort((a, b) =>
 | 
						|
      a.invertedFullName
 | 
						|
        .toLowerCase()
 | 
						|
        .localeCompare(b.invertedFullName.toLocaleLowerCase())
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  public get isEmpty(): boolean {
 | 
						|
    return this.list.length === 0;
 | 
						|
  }
 | 
						|
 | 
						|
  public get size(): number {
 | 
						|
    return this.list.length;
 | 
						|
  }
 | 
						|
 | 
						|
  public get fullList(): Member[] {
 | 
						|
    return this.list;
 | 
						|
  }
 | 
						|
 | 
						|
  filter(predicate: (m: Member) => boolean): Member[] {
 | 
						|
    return this.list.filter(predicate);
 | 
						|
  }
 | 
						|
 | 
						|
  get(id: number): Member | undefined {
 | 
						|
    return this.map.get(id);
 | 
						|
  }
 | 
						|
 | 
						|
  children(id: number): Member[] {
 | 
						|
    return this.list.filter((m) => m.mother === id || m.father === id);
 | 
						|
  }
 | 
						|
 | 
						|
  childrenOfCouple(c: Couple): Member[] {
 | 
						|
    if (!c.husband && !c.wife) return [];
 | 
						|
    return this.list.filter(
 | 
						|
      (m) => m.mother === c.wife && m.father === c.husband
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  siblings(id: number): Member[] {
 | 
						|
    const p = this.get(id);
 | 
						|
    return this.list.filter(
 | 
						|
      (m) =>
 | 
						|
        m.id !== p?.id &&
 | 
						|
        ((m.mother && m.mother === p?.mother) ||
 | 
						|
          (m.father && m.father === p?.father))
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export class MemberApi {
 | 
						|
  /**
 | 
						|
   * Create a new member
 | 
						|
   */
 | 
						|
  static async Create(m: Member): Promise<Member> {
 | 
						|
    const res = await APIClient.exec({
 | 
						|
      uri: `/family/${m.family_id}/genealogy/member/create`,
 | 
						|
      method: "POST",
 | 
						|
      jsonData: m,
 | 
						|
    });
 | 
						|
 | 
						|
    return new Member(res.data);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Get the information about a single member
 | 
						|
   */
 | 
						|
  static async GetSingle(
 | 
						|
    family_id: number,
 | 
						|
    member_id: number
 | 
						|
  ): Promise<Member> {
 | 
						|
    const res = await APIClient.exec({
 | 
						|
      uri: `/family/${family_id}/genealogy/member/${member_id}`,
 | 
						|
      method: "GET",
 | 
						|
    });
 | 
						|
 | 
						|
    return new Member(res.data);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Get the entire list of family members of a family
 | 
						|
   */
 | 
						|
  static async GetEntireList(family_id: number): Promise<MembersList> {
 | 
						|
    const res = await APIClient.exec({
 | 
						|
      uri: `/family/${family_id}/genealogy/members`,
 | 
						|
      method: "GET",
 | 
						|
    });
 | 
						|
 | 
						|
    return new MembersList(res.data.map((d: any) => new Member(d)));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Update a member information
 | 
						|
   */
 | 
						|
  static async Update(m: Member): Promise<void> {
 | 
						|
    await APIClient.exec({
 | 
						|
      uri: `/family/${m.family_id}/genealogy/member/${m.id}`,
 | 
						|
      method: "PUT",
 | 
						|
      jsonData: m,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Set a new photo for a member
 | 
						|
   */
 | 
						|
  static async SetMemberPhoto(m: Member, b: Blob): Promise<void> {
 | 
						|
    const fd = new FormData();
 | 
						|
    fd.append("photo", b);
 | 
						|
    await APIClient.exec({
 | 
						|
      uri: `/family/${m.family_id}/genealogy/member/${m.id}/photo`,
 | 
						|
      method: "PUT",
 | 
						|
      formData: fd,
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Remove the photo of a member
 | 
						|
   */
 | 
						|
  static async RemoveMemberPhoto(m: Member): Promise<void> {
 | 
						|
    await APIClient.exec({
 | 
						|
      uri: `/family/${m.family_id}/genealogy/member/${m.id}/photo`,
 | 
						|
      method: "DELETE",
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Delete a family member
 | 
						|
   */
 | 
						|
  static async Delete(m: Member): Promise<void> {
 | 
						|
    await APIClient.exec({
 | 
						|
      uri: `/family/${m.family_id}/genealogy/member/${m.id}`,
 | 
						|
      method: "DELETE",
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 |