import ClearIcon from "@mui/icons-material/Clear"; import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import FileDownloadIcon from "@mui/icons-material/FileDownload"; import SaveIcon from "@mui/icons-material/Save"; import { Button, Grid, ListItemAvatar, ListItemButton, ListItemText, Stack, } from "@mui/material"; import * as EmailValidator from "email-validator"; import React from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Couple } from "../../api/CoupleApi"; import { Member, MemberApi, fmtDate } from "../../api/MemberApi"; import { ServerApi } from "../../api/ServerApi"; import { useAlert } from "../../hooks/context_providers/AlertDialogProvider"; import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider"; import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider"; import { AsyncWidget } from "../../widgets/AsyncWidget"; import { useFamily } from "../../widgets/BaseFamilyRoute"; import { ConfirmLeaveWithoutSaveDialog } from "../../widgets/ConfirmLeaveWithoutSaveDialog"; import { CouplePhoto } from "../../widgets/CouplePhoto"; import { FamilyPageTitle } from "../../widgets/FamilyPageTitle"; import { MemberItem } from "../../widgets/MemberItem"; import { MemberPhoto } from "../../widgets/MemberPhoto"; import { PropertiesBox } from "../../widgets/PropertiesBox"; import { RouterLink } from "../../widgets/RouterLink"; import { DateInput } from "../../widgets/forms/DateInput"; import { MemberInput } from "../../widgets/forms/MemberInput"; import { PropCheckbox } from "../../widgets/forms/PropCheckbox"; import { PropEdit } from "../../widgets/forms/PropEdit"; import { PropSelect } from "../../widgets/forms/SelectInput"; import { SexSelection } from "../../widgets/forms/SexSelection"; import { UploadPhotoButton } from "../../widgets/forms/UploadPhotoButton"; import { useQuery } from "../../hooks/useQuery"; /** * Create a new member route */ export function FamilyCreateMemberRoute(): React.ReactElement { const alert = useAlert(); const snackbar = useSnackbar(); const [shouldQuit, setShouldQuit] = React.useState(false); const n = useNavigate(); const family = useFamily(); const parameters = useQuery(); const mother = Number(parameters.get("mother")); const father = Number(parameters.get("father")); const create = async (m: Member) => { try { const r = await MemberApi.Create(m); await family.reloadMembersList(); setShouldQuit(true); n(family.family.URL(`member/${r.id}`)); snackbar(`La fiche pour ${r.fullName} a été créée avec succès !`); } catch (e) { console.error(e); alert("Echec de la création de la personne !"); } }; const cancel = () => { setShouldQuit(true); n(family.family.URL("members")); }; const member = Member.New(family.family.family_id); if (mother) member.mother = mother; if (father) member.father = father; return ( ); } /** * Get existing member route */ export function FamilyMemberRoute(): React.ReactElement { const count = React.useRef(1); const n = useNavigate(); const alert = useAlert(); const confirm = useConfirm(); const snackbar = useSnackbar(); const family = useFamily(); const { memberId } = useParams(); const [member, setMember] = React.useState(); const load = async () => { setMember(await MemberApi.GetSingle(family.familyId, Number(memberId))); }; const forceReload = async () => { count.current += 1; setMember(undefined); await family.reloadMembersList(); }; const deleteMember = async () => { try { if ( !(await confirm( "Voulez-vous vraiment supprimer cette fiche membre ? L'opération n'est pas réversible !" )) ) return; await MemberApi.Delete(member!); snackbar("La fiche de membre a été supprimée avec succès !"); n(family.family.URL("members")); await family.reloadMembersList(); } catch (e) { console.error(e); alert("Échec de la suppression du membre !"); } }; return ( ( n(family.family.URL(`member/${member!.id}/edit`)) } onForceReload={forceReload} /> )} /> ); } /** * Edit existing member route */ export function FamilyEditMemberRoute(): React.ReactElement { const n = useNavigate(); const { memberId } = useParams(); const alert = useAlert(); const snackbar = useSnackbar(); const [shouldQuit, setShouldQuit] = React.useState(false); const family = useFamily(); const [member, setMember] = React.useState(); const load = async () => { setMember(await MemberApi.GetSingle(family.familyId, Number(memberId))); }; const cancel = () => { setShouldQuit(true); //n(family.family.URL(`member/${member!.id}`)); n(-1); }; const save = async (m: Member) => { try { await MemberApi.Update(m); snackbar("Les informations du membre ont été mises à jour avec succès !"); await family.reloadMembersList(); setShouldQuit(true); n(family.family.URL(`member/${member!.id}`)); } catch (e) { console.error(e); alert("Échec de la mise à jour des informations du membre !"); } }; return ( ( )} /> ); } export function MemberPage(p: { member: Member; editing: boolean; creating: boolean; shouldAllowLeaving?: boolean; children?: Member[]; siblings?: Member[]; couples?: Couple[]; onCancel?: () => void; onSave?: (m: Member) => void; onRequestEdit?: () => void; onRequestDelete?: () => void; onForceReload?: () => void; }): React.ReactElement { const confirm = useConfirm(); const snackbar = useSnackbar(); const family = useFamily(); const [changed, setChanged] = React.useState(false); const [member, setMember] = React.useState( new Member(structuredClone(p.member)) ); const updatedMember = () => { setChanged(true); setMember(new Member(structuredClone(member))); }; const save = () => { p.onSave!(member); }; const cancel = async () => { if ( changed && !(await confirm( "Voulez-vous vraiment retirer les modifications apportées ? Celles-ci seront perdues !" )) ) return; p.onCancel!(); }; const uploadNewPhoto = async (b: Blob) => { await MemberApi.SetMemberPhoto(member, b); snackbar("La photo du membre a été mise à jour avec succès !"); p.onForceReload?.(); }; const deletePhoto = async () => { try { if (!(await confirm("Voulez-vous supprimer cette photo ?"))) return; await MemberApi.RemoveMemberPhoto(member); snackbar("La photo du membre a été supprimée avec succès !"); p.onForceReload?.(); } catch (e) { console.error(e); alert("Échec de la suppresion de la photo !"); } }; return (
{/* Edit button */} {p.onRequestEdit && ( )} {/* Delete button */} {p.onRequestDelete && ( )} {/* Save button */} {p.editing && changed && ( )} {/* Cancel button */} {p.editing && ( )}
{/* General info */} {/* Sex */} { member.sex = v; updatedMember(); }} /> {/* First name */} { member.first_name = v; updatedMember(); }} size={ServerApi.Config.constraints.member_first_name} /> {/* Last name */} { member.last_name = v; updatedMember(); }} size={ServerApi.Config.constraints.member_last_name} /> {/* Birth last name */} { member.birth_last_name = v; updatedMember(); }} size={ServerApi.Config.constraints.member_birth_last_name} /> {/* Birth day */} { member.birth_year = d.year; member.birth_month = d.month; member.birth_day = d.day; updatedMember(); }} /> {/* Is dead */} { member.dead = v; updatedMember(); }} /> {/* Death day */} { member.death_year = d.year; member.death_month = d.month; member.death_day = d.day; updatedMember(); }} /> {/* Father */}
{ member.father = m; updatedMember(); }} filter={(m) => (m.sex === "M" || m.sex === undefined) && m.id !== member.id } current={member.father} /> {/* Mother */}
{ member.mother = m; updatedMember(); }} filter={(m) => (m.sex === "F" || m.sex === undefined) && m.id !== member.id } current={member.mother} />
{/* Photo */}

{p.editing ? (

Veuillez enregistrer / annuler les modifications apportées à la fiche avant de changer la photo du membre.

) : ( <> {" "} {member.hasPhoto && ( )}{" "} {member.hasPhoto && ( )} )}{" "}
{/* Contact */} {(p.editing || member.hasContactInfo) && ( {/* Email */} { member.email = v; updatedMember(); }} size={ServerApi.Config.constraints.member_email} checkValue={(e) => EmailValidator.validate(e)} /> {/* Phone number */} { member.phone = v; updatedMember(); }} size={ServerApi.Config.constraints.member_phone} /> {/* Country */} { member.country = o; updatedMember(); }} value={member.country} options={ServerApi.Config.countries.map((c) => { return { label: c.fr, value: c.code }; })} /> {/* Address */} { member.address = v; updatedMember(); }} size={ServerApi.Config.constraints.member_address} /> {/* Postal code */} { member.postal_code = v; updatedMember(); }} size={ServerApi.Config.constraints.member_postal_code} /> {/* City */} { member.city = v; updatedMember(); }} size={ServerApi.Config.constraints.member_city} /> )} {/* Bio */} {(p.editing || member.hasNote) && ( { member.note = v; updatedMember(); }} size={ServerApi.Config.constraints.member_note} /> )} {/* Couples */} {p.couples && ( {p.couples!.length === 0 ? ( <>{member.sex === "F" ? "Aucun époux" : "Aucune épouse"} ) : ( p.couples.map((c) => ( )) )} )} {/* Children */} {p.children && ( {p.children.length === 0 ? ( <>Aucun enfant ) : ( p.children.map((c) => ( )) )} )} {/* Siblings */} {p.siblings && ( {p.siblings.length === 0 ? ( <>Aucun frère ou sœur ) : ( p.siblings.map((c) => ( )) )} {(member.mother || member.father) && (
)}
)}
); } function CoupleItem(p: { currMemberId: number; couple: Couple; }): React.ReactElement { const n = useNavigate(); const family = useFamily(); const statusStr = ServerApi.Config.couples_states.find( (c) => c.code === p.couple.state )?.fr; const status = []; if (statusStr) status.push(statusStr); if (p.couple.dateOfWedding) status.push(`Mariage : ${fmtDate(p.couple.dateOfWedding)}`); if (p.couple.dateOfDivorce) status.push(`Divorce : ${fmtDate(p.couple.dateOfDivorce)}`); const otherSpouseID = p.couple.wife === p.currMemberId ? p.couple.husband : p.couple.wife; const otherSpouse = otherSpouseID ? family.members.get(otherSpouseID) : undefined; return ( n(family.family.coupleURL(p.couple))}> {p.couple.hasPhoto ? ( ) : ( )} ); }