Make members information globally available
This commit is contained in:
		| @@ -2,7 +2,7 @@ import { APIClient } from "./ApiClient"; | ||||
|  | ||||
| export type Sex = "M" | "F"; | ||||
|  | ||||
| export interface MemberApi { | ||||
| export interface MemberDataApi { | ||||
|   id: number; | ||||
|   family_id: number; | ||||
|   first_name?: string; | ||||
| @@ -31,7 +31,7 @@ export interface MemberApi { | ||||
|   note?: string; | ||||
| } | ||||
|  | ||||
| export class Member implements MemberApi { | ||||
| export class Member implements MemberDataApi { | ||||
|   id: number; | ||||
|   family_id: number; | ||||
|   first_name?: string; | ||||
| @@ -59,7 +59,7 @@ export class Member implements MemberApi { | ||||
|   death_day?: number; | ||||
|   note?: string; | ||||
|  | ||||
|   constructor(m: MemberApi) { | ||||
|   constructor(m: MemberDataApi) { | ||||
|     this.id = m.id; | ||||
|     this.family_id = m.family_id; | ||||
|     this.first_name = m.first_name; | ||||
| @@ -108,6 +108,20 @@ export class Member implements MemberApi { | ||||
|   } | ||||
| } | ||||
|  | ||||
| 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); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class MemberApi { | ||||
|   /** | ||||
|    * Create a new member | ||||
| @@ -137,6 +151,18 @@ export class MemberApi { | ||||
|     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}/members`, | ||||
|       method: "GET", | ||||
|     }); | ||||
|  | ||||
|     return new MembersList(res.data.map((d: any) => new Member(d))); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Update a member information | ||||
|    */ | ||||
|   | ||||
| @@ -33,7 +33,7 @@ export function FamilyCreateMemberRoute(): React.ReactElement { | ||||
|     try { | ||||
|       const r = await MemberApi.Create(m); | ||||
|  | ||||
|       // TODO : trigger update | ||||
|       await family.reloadMembersList(); | ||||
|  | ||||
|       setShouldQuit(true); | ||||
|       n(family.family.URL(`member/${r.id}`)); | ||||
| @@ -92,7 +92,7 @@ export function FamilyMemberRoute(): React.ReactElement { | ||||
|       snackbar("La fiche de membre a été supprimée avec succès !"); | ||||
|       n(family.family.URL("members")); | ||||
|  | ||||
|       // TODO : refresh cached members list | ||||
|       await family.reloadMembersList(); | ||||
|     } catch (e) { | ||||
|       console.error(e); | ||||
|       alert("Échec de la suppression du membre !"); | ||||
| @@ -148,7 +148,7 @@ export function FamilyEditMemberRoute(): React.ReactElement { | ||||
|  | ||||
|       snackbar("Les informations du membre ont été mises à jour avec succès !"); | ||||
|  | ||||
|       // TODO : update family hook info | ||||
|       await family.reloadMembersList(); | ||||
|  | ||||
|       setShouldQuit(true); | ||||
|       n(family.family.URL(`member/${member!.id}`)); | ||||
| @@ -193,7 +193,6 @@ export function MemberPage(p: { | ||||
|   const [member, setMember] = React.useState(structuredClone(p.member)); | ||||
|  | ||||
|   const updatedMember = () => { | ||||
|     // TODO : add confirmation | ||||
|     setChanged(true); | ||||
|     setMember(structuredClone(member)); | ||||
|   }; | ||||
|   | ||||
| @@ -30,11 +30,14 @@ import { RouterLink } from "./RouterLink"; | ||||
| import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; | ||||
| import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider"; | ||||
| import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; | ||||
| import { Member, MemberApi, MembersList } from "../api/MemberApi"; | ||||
|  | ||||
| interface FamilyContext { | ||||
|   family: Family; | ||||
|   members: MembersList; | ||||
|   familyId: number; | ||||
|   reloadFamilyInfo: () => void; | ||||
|   reloadMembersList: () => Promise<void>; | ||||
| } | ||||
|  | ||||
| const FamilyContextK = React.createContext<FamilyContext | null>(null); | ||||
| @@ -46,16 +49,26 @@ export function BaseFamilyRoute(): React.ReactElement { | ||||
|   const confirm = useConfirm(); | ||||
|  | ||||
|   const [family, setFamily] = React.useState<null | Family>(null); | ||||
|   const [members, setMembers] = React.useState<null | MembersList>(null); | ||||
|  | ||||
|   const loadKey = React.useRef(1); | ||||
|  | ||||
|   const loadPromise = React.useRef<() => void>(); | ||||
|  | ||||
|   const load = async () => { | ||||
|     setFamily(await FamilyApi.GetSingle(Number(familyId))); | ||||
|     const familyID = Number(familyId); | ||||
|     setFamily(await FamilyApi.GetSingle(familyID)); | ||||
|     setMembers(await MemberApi.GetEntireList(familyID)); | ||||
|   }; | ||||
|  | ||||
|   const onReload = () => { | ||||
|   const onReload = async () => { | ||||
|     loadKey.current += 1; | ||||
|     setFamily(null); | ||||
|     setMembers(null); | ||||
|  | ||||
|     return new Promise<void>((res, _rej) => { | ||||
|       loadPromise.current = () => res(); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   const copyInvitationCode = async () => { | ||||
| @@ -85,113 +98,122 @@ export function BaseFamilyRoute(): React.ReactElement { | ||||
|  | ||||
|   return ( | ||||
|     <AsyncWidget | ||||
|       ready={family != null} | ||||
|       ready={family !== null && members !== null} | ||||
|       loadKey={`${familyId}-${loadKey.current}`} | ||||
|       load={load} | ||||
|       errMsg="Échec du chargement des informations de la famille !" | ||||
|       build={() => ( | ||||
|         <FamilyContextK.Provider | ||||
|           value={{ | ||||
|             family: family!, | ||||
|             familyId: family!.family_id, | ||||
|             reloadFamilyInfo: onReload, | ||||
|           }} | ||||
|         > | ||||
|           <Box | ||||
|             sx={{ | ||||
|               display: "flex", | ||||
|               flex: "2", | ||||
|       build={() => { | ||||
|         if (loadPromise.current != null) { | ||||
|           loadPromise.current?.(); | ||||
|           loadPromise.current = undefined; | ||||
|         } | ||||
|  | ||||
|         return ( | ||||
|           <FamilyContextK.Provider | ||||
|             value={{ | ||||
|               family: family!, | ||||
|               members: members!, | ||||
|               familyId: family!.family_id, | ||||
|               reloadFamilyInfo: onReload, | ||||
|               reloadMembersList: onReload, | ||||
|             }} | ||||
|           > | ||||
|             <List | ||||
|               component="nav" | ||||
|             <Box | ||||
|               sx={{ | ||||
|                 minWidth: "280px", | ||||
|                 backgroundColor: "background.paper", | ||||
|                 display: "flex", | ||||
|                 flex: "2", | ||||
|               }} | ||||
|             > | ||||
|               <FamilyLink icon={<HomeIcon />} label="Accueil" uri="" /> | ||||
|               <List | ||||
|                 component="nav" | ||||
|                 sx={{ | ||||
|                   minWidth: "280px", | ||||
|                   backgroundColor: "background.paper", | ||||
|                 }} | ||||
|               > | ||||
|                 <FamilyLink icon={<HomeIcon />} label="Accueil" uri="" /> | ||||
|  | ||||
|               <FamilyLink | ||||
|                 icon={<Icon path={mdiCrowd} size={1} />} | ||||
|                 label="Membres" | ||||
|                 uri="members" | ||||
|               /> | ||||
|                 <FamilyLink | ||||
|                   icon={<Icon path={mdiCrowd} size={1} />} | ||||
|                   label="Membres" | ||||
|                   uri="members" | ||||
|                 /> | ||||
|  | ||||
|               <FamilyLink | ||||
|                 icon={<Icon path={mdiHumanMaleFemale} size={1} />} | ||||
|                 label="Couples" | ||||
|                 uri="couples" | ||||
|               /> | ||||
|                 <FamilyLink | ||||
|                   icon={<Icon path={mdiHumanMaleFemale} size={1} />} | ||||
|                   label="Couples" | ||||
|                   uri="couples" | ||||
|                 /> | ||||
|  | ||||
|               <FamilyLink | ||||
|                 icon={<Icon path={mdiFamilyTree} size={1} />} | ||||
|                 label="Arbre" | ||||
|                 uri="tree" | ||||
|               /> | ||||
|                 <FamilyLink | ||||
|                   icon={<Icon path={mdiFamilyTree} size={1} />} | ||||
|                   label="Arbre" | ||||
|                   uri="tree" | ||||
|                 /> | ||||
|  | ||||
|               <Divider sx={{ my: 1 }} /> | ||||
|               <ListSubheader component="div">Administration</ListSubheader> | ||||
|                 <Divider sx={{ my: 1 }} /> | ||||
|                 <ListSubheader component="div">Administration</ListSubheader> | ||||
|  | ||||
|               <FamilyLink | ||||
|                 icon={<Icon path={mdiAccountMultiple} size={1} />} | ||||
|                 label="Utilisateurs" | ||||
|                 uri="users" | ||||
|               /> | ||||
|                 <FamilyLink | ||||
|                   icon={<Icon path={mdiAccountMultiple} size={1} />} | ||||
|                   label="Utilisateurs" | ||||
|                   uri="users" | ||||
|                 /> | ||||
|  | ||||
|               <FamilyLink | ||||
|                 icon={<Icon path={mdiCog} size={1} />} | ||||
|                 label="Paramètres" | ||||
|                 uri="settings" | ||||
|               /> | ||||
|                 <FamilyLink | ||||
|                   icon={<Icon path={mdiCog} size={1} />} | ||||
|                   label="Paramètres" | ||||
|                   uri="settings" | ||||
|                 /> | ||||
|  | ||||
|               {/* Invitation code */} | ||||
|                 {/* Invitation code */} | ||||
|  | ||||
|               <ListItem | ||||
|                 dense | ||||
|                 secondaryAction={ | ||||
|                   <span> | ||||
|                     <Tooltip title="Copier le code d'invitation dans le presse papier"> | ||||
|                       <IconButton onClick={copyInvitationCode}> | ||||
|                         <Icon path={mdiContentCopy} size={0.75} /> | ||||
|                       </IconButton> | ||||
|                     </Tooltip> | ||||
|  | ||||
|                     {family!.is_admin && ( | ||||
|                       <Tooltip title="Changer le code d'activation"> | ||||
|                         <IconButton onClick={changeInvitationCode}> | ||||
|                           <Icon path={mdiRefresh} size={0.75} /> | ||||
|                 <ListItem | ||||
|                   dense | ||||
|                   secondaryAction={ | ||||
|                     <span> | ||||
|                       <Tooltip title="Copier le code d'invitation dans le presse papier"> | ||||
|                         <IconButton onClick={copyInvitationCode}> | ||||
|                           <Icon path={mdiContentCopy} size={0.75} /> | ||||
|                         </IconButton> | ||||
|                       </Tooltip> | ||||
|                     )} | ||||
|                   </span> | ||||
|                 } | ||||
|               > | ||||
|                 <ListItemIcon> | ||||
|                   <Icon path={mdiLockCheck} size={1} /> | ||||
|                 </ListItemIcon> | ||||
|                 <ListItemText | ||||
|                   primary="Code d'invitation" | ||||
|                   secondary={family?.invitation_code} | ||||
|                 /> | ||||
|               </ListItem> | ||||
|             </List> | ||||
|  | ||||
|             <Box | ||||
|               component="main" | ||||
|               sx={{ | ||||
|                 flexGrow: 1, | ||||
|                 overflow: "auto", | ||||
|                 padding: "20px", | ||||
|                 display: "flex", | ||||
|                 flexDirection: "column", | ||||
|               }} | ||||
|             > | ||||
|               <Outlet /> | ||||
|                       {family!.is_admin && ( | ||||
|                         <Tooltip title="Changer le code d'activation"> | ||||
|                           <IconButton onClick={changeInvitationCode}> | ||||
|                             <Icon path={mdiRefresh} size={0.75} /> | ||||
|                           </IconButton> | ||||
|                         </Tooltip> | ||||
|                       )} | ||||
|                     </span> | ||||
|                   } | ||||
|                 > | ||||
|                   <ListItemIcon> | ||||
|                     <Icon path={mdiLockCheck} size={1} /> | ||||
|                   </ListItemIcon> | ||||
|                   <ListItemText | ||||
|                     primary="Code d'invitation" | ||||
|                     secondary={family?.invitation_code} | ||||
|                   /> | ||||
|                 </ListItem> | ||||
|               </List> | ||||
|  | ||||
|               <Box | ||||
|                 component="main" | ||||
|                 sx={{ | ||||
|                   flexGrow: 1, | ||||
|                   overflow: "auto", | ||||
|                   padding: "20px", | ||||
|                   display: "flex", | ||||
|                   flexDirection: "column", | ||||
|                 }} | ||||
|               > | ||||
|                 <Outlet /> | ||||
|               </Box> | ||||
|             </Box> | ||||
|           </Box> | ||||
|         </FamilyContextK.Provider> | ||||
|       )} | ||||
|           </FamilyContextK.Provider> | ||||
|         ); | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user