Make members information globally available

This commit is contained in:
Pierre HUBERT 2023-08-09 08:55:37 +02:00
parent 1128b5ebd4
commit 359dd2f9ee
3 changed files with 143 additions and 96 deletions

View File

@ -2,7 +2,7 @@ import { APIClient } from "./ApiClient";
export type Sex = "M" | "F"; export type Sex = "M" | "F";
export interface MemberApi { export interface MemberDataApi {
id: number; id: number;
family_id: number; family_id: number;
first_name?: string; first_name?: string;
@ -31,7 +31,7 @@ export interface MemberApi {
note?: string; note?: string;
} }
export class Member implements MemberApi { export class Member implements MemberDataApi {
id: number; id: number;
family_id: number; family_id: number;
first_name?: string; first_name?: string;
@ -59,7 +59,7 @@ export class Member implements MemberApi {
death_day?: number; death_day?: number;
note?: string; note?: string;
constructor(m: MemberApi) { constructor(m: MemberDataApi) {
this.id = m.id; this.id = m.id;
this.family_id = m.family_id; this.family_id = m.family_id;
this.first_name = m.first_name; 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 { export class MemberApi {
/** /**
* Create a new member * Create a new member
@ -137,6 +151,18 @@ export class MemberApi {
return new Member(res.data); 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 * Update a member information
*/ */

View File

@ -33,7 +33,7 @@ export function FamilyCreateMemberRoute(): React.ReactElement {
try { try {
const r = await MemberApi.Create(m); const r = await MemberApi.Create(m);
// TODO : trigger update await family.reloadMembersList();
setShouldQuit(true); setShouldQuit(true);
n(family.family.URL(`member/${r.id}`)); 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 !"); snackbar("La fiche de membre a été supprimée avec succès !");
n(family.family.URL("members")); n(family.family.URL("members"));
// TODO : refresh cached members list await family.reloadMembersList();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
alert("Échec de la suppression du membre !"); 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 !"); snackbar("Les informations du membre ont été mises à jour avec succès !");
// TODO : update family hook info await family.reloadMembersList();
setShouldQuit(true); setShouldQuit(true);
n(family.family.URL(`member/${member!.id}`)); n(family.family.URL(`member/${member!.id}`));
@ -193,7 +193,6 @@ export function MemberPage(p: {
const [member, setMember] = React.useState(structuredClone(p.member)); const [member, setMember] = React.useState(structuredClone(p.member));
const updatedMember = () => { const updatedMember = () => {
// TODO : add confirmation
setChanged(true); setChanged(true);
setMember(structuredClone(member)); setMember(structuredClone(member));
}; };

View File

@ -30,11 +30,14 @@ import { RouterLink } from "./RouterLink";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider"; import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { Member, MemberApi, MembersList } from "../api/MemberApi";
interface FamilyContext { interface FamilyContext {
family: Family; family: Family;
members: MembersList;
familyId: number; familyId: number;
reloadFamilyInfo: () => void; reloadFamilyInfo: () => void;
reloadMembersList: () => Promise<void>;
} }
const FamilyContextK = React.createContext<FamilyContext | null>(null); const FamilyContextK = React.createContext<FamilyContext | null>(null);
@ -46,16 +49,26 @@ export function BaseFamilyRoute(): React.ReactElement {
const confirm = useConfirm(); const confirm = useConfirm();
const [family, setFamily] = React.useState<null | Family>(null); const [family, setFamily] = React.useState<null | Family>(null);
const [members, setMembers] = React.useState<null | MembersList>(null);
const loadKey = React.useRef(1); const loadKey = React.useRef(1);
const loadPromise = React.useRef<() => void>();
const load = async () => { 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; loadKey.current += 1;
setFamily(null); setFamily(null);
setMembers(null);
return new Promise<void>((res, _rej) => {
loadPromise.current = () => res();
});
}; };
const copyInvitationCode = async () => { const copyInvitationCode = async () => {
@ -85,113 +98,122 @@ export function BaseFamilyRoute(): React.ReactElement {
return ( return (
<AsyncWidget <AsyncWidget
ready={family != null} ready={family !== null && members !== null}
loadKey={`${familyId}-${loadKey.current}`} loadKey={`${familyId}-${loadKey.current}`}
load={load} load={load}
errMsg="Échec du chargement des informations de la famille !" errMsg="Échec du chargement des informations de la famille !"
build={() => ( build={() => {
<FamilyContextK.Provider if (loadPromise.current != null) {
value={{ loadPromise.current?.();
family: family!, loadPromise.current = undefined;
familyId: family!.family_id, }
reloadFamilyInfo: onReload,
}} return (
> <FamilyContextK.Provider
<Box value={{
sx={{ family: family!,
display: "flex", members: members!,
flex: "2", familyId: family!.family_id,
reloadFamilyInfo: onReload,
reloadMembersList: onReload,
}} }}
> >
<List <Box
component="nav"
sx={{ sx={{
minWidth: "280px", display: "flex",
backgroundColor: "background.paper", flex: "2",
}} }}
> >
<FamilyLink icon={<HomeIcon />} label="Accueil" uri="" /> <List
component="nav"
sx={{
minWidth: "280px",
backgroundColor: "background.paper",
}}
>
<FamilyLink icon={<HomeIcon />} label="Accueil" uri="" />
<FamilyLink <FamilyLink
icon={<Icon path={mdiCrowd} size={1} />} icon={<Icon path={mdiCrowd} size={1} />}
label="Membres" label="Membres"
uri="members" uri="members"
/> />
<FamilyLink <FamilyLink
icon={<Icon path={mdiHumanMaleFemale} size={1} />} icon={<Icon path={mdiHumanMaleFemale} size={1} />}
label="Couples" label="Couples"
uri="couples" uri="couples"
/> />
<FamilyLink <FamilyLink
icon={<Icon path={mdiFamilyTree} size={1} />} icon={<Icon path={mdiFamilyTree} size={1} />}
label="Arbre" label="Arbre"
uri="tree" uri="tree"
/> />
<Divider sx={{ my: 1 }} /> <Divider sx={{ my: 1 }} />
<ListSubheader component="div">Administration</ListSubheader> <ListSubheader component="div">Administration</ListSubheader>
<FamilyLink <FamilyLink
icon={<Icon path={mdiAccountMultiple} size={1} />} icon={<Icon path={mdiAccountMultiple} size={1} />}
label="Utilisateurs" label="Utilisateurs"
uri="users" uri="users"
/> />
<FamilyLink <FamilyLink
icon={<Icon path={mdiCog} size={1} />} icon={<Icon path={mdiCog} size={1} />}
label="Paramètres" label="Paramètres"
uri="settings" uri="settings"
/> />
{/* Invitation code */} {/* Invitation code */}
<ListItem <ListItem
dense dense
secondaryAction={ secondaryAction={
<span> <span>
<Tooltip title="Copier le code d'invitation dans le presse papier"> <Tooltip title="Copier le code d'invitation dans le presse papier">
<IconButton onClick={copyInvitationCode}> <IconButton onClick={copyInvitationCode}>
<Icon path={mdiContentCopy} size={0.75} /> <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} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)}
</span>
}
>
<ListItemIcon>
<Icon path={mdiLockCheck} size={1} />
</ListItemIcon>
<ListItemText
primary="Code d'invitation"
secondary={family?.invitation_code}
/>
</ListItem>
</List>
<Box {family!.is_admin && (
component="main" <Tooltip title="Changer le code d'activation">
sx={{ <IconButton onClick={changeInvitationCode}>
flexGrow: 1, <Icon path={mdiRefresh} size={0.75} />
overflow: "auto", </IconButton>
padding: "20px", </Tooltip>
display: "flex", )}
flexDirection: "column", </span>
}} }
> >
<Outlet /> <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>
</Box> </FamilyContextK.Provider>
</FamilyContextK.Provider> );
)} }}
/> />
); );
} }