From 6c82104cdc1b649465da569ad6b875921d1af4b3 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Fri, 18 Aug 2023 15:10:16 +0200 Subject: [PATCH] Can export data from UI --- geneit_app/src/api/DataApi.ts | 17 ++ .../LoadingMessageProvider.tsx | 67 +++++ geneit_app/src/index.tsx | 19 +- .../src/routes/family/FamilySettingsRoute.tsx | 229 ++++++++++++------ geneit_app/src/utils/blob_utils.ts | 10 + geneit_app/src/widgets/BaseFamilyRoute.tsx | 4 + geneit_app/src/widgets/FamilyCard.tsx | 15 ++ 7 files changed, 273 insertions(+), 88 deletions(-) create mode 100644 geneit_app/src/api/DataApi.ts create mode 100644 geneit_app/src/hooks/context_providers/LoadingMessageProvider.tsx create mode 100644 geneit_app/src/utils/blob_utils.ts create mode 100644 geneit_app/src/widgets/FamilyCard.tsx diff --git a/geneit_app/src/api/DataApi.ts b/geneit_app/src/api/DataApi.ts new file mode 100644 index 0000000..9916cfd --- /dev/null +++ b/geneit_app/src/api/DataApi.ts @@ -0,0 +1,17 @@ +import { APIClient } from "./ApiClient"; + +/** + * Data management api client + */ +export class DataApi { + /** + * Export the data of a family + */ + static async ExportData(family_id: number): Promise { + const res = await APIClient.exec({ + uri: `/family/${family_id}/data/export`, + method: "GET", + }); + return res.data; + } +} diff --git a/geneit_app/src/hooks/context_providers/LoadingMessageProvider.tsx b/geneit_app/src/hooks/context_providers/LoadingMessageProvider.tsx new file mode 100644 index 0000000..ef37f02 --- /dev/null +++ b/geneit_app/src/hooks/context_providers/LoadingMessageProvider.tsx @@ -0,0 +1,67 @@ +import { + Button, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from "@mui/material"; +import React, { PropsWithChildren } from "react"; + +type LoadingMessageContext = { + show: (message: string) => void; + hide: () => void; +}; + +const LoadingMessageContextK = + React.createContext(null); + +export function LoadingMessageProvider( + p: PropsWithChildren +): React.ReactElement { + const [open, setOpen] = React.useState(false); + + const [message, setMessage] = React.useState(""); + + const hook: LoadingMessageContext = { + show(message) { + setMessage(message); + setOpen(true); + }, + hide() { + setMessage(""); + setOpen(false); + }, + }; + + return ( + <> + + {p.children} + + + + + +
+ + + {message} +
+
+
+
+ + ); +} + +export function useLoadingMessage(): LoadingMessageContext { + return React.useContext(LoadingMessageContextK)!; +} diff --git a/geneit_app/src/index.tsx b/geneit_app/src/index.tsx index 9692f39..10e804b 100644 --- a/geneit_app/src/index.tsx +++ b/geneit_app/src/index.tsx @@ -15,6 +15,7 @@ import { ConfirmDialogProvider } from "./hooks/context_providers/ConfirmDialogPr import { DarkThemeProvider } from "./hooks/context_providers/DarkThemeProvider"; import { SnackbarProvider } from "./hooks/context_providers/SnackbarProvider"; import { AsyncWidget } from "./widgets/AsyncWidget"; +import { LoadingMessageProvider } from "./hooks/context_providers/LoadingMessageProvider"; async function init() { try { @@ -28,14 +29,16 @@ async function init() { -
- await ServerApi.LoadConfig()} - errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !" - build={() => } - /> -
+ +
+ await ServerApi.LoadConfig()} + errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !" + build={() => } + /> +
+
diff --git a/geneit_app/src/routes/family/FamilySettingsRoute.tsx b/geneit_app/src/routes/family/FamilySettingsRoute.tsx index 90aa4f9..c761d5a 100644 --- a/geneit_app/src/routes/family/FamilySettingsRoute.tsx +++ b/geneit_app/src/routes/family/FamilySettingsRoute.tsx @@ -16,6 +16,11 @@ import { useAlert } from "../../hooks/context_providers/AlertDialogProvider"; import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider"; import { useFamily } from "../../widgets/BaseFamilyRoute"; import { formatDate } from "../../widgets/TimeWidget"; +import { FamilyCard } from "../../widgets/FamilyCard"; +import DownloadIcon from "@mui/icons-material/Download"; +import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider"; +import { DataApi } from "../../api/DataApi"; +import { downloadBlob } from "../../utils/blob_utils"; export function FamilySettingsRoute(): React.ReactElement { const alert = useAlert(); @@ -24,32 +29,6 @@ export function FamilySettingsRoute(): React.ReactElement { const family = useFamily(); - const [newName, setNewName] = React.useState(family.family.name); - - const canEdit = family.family.is_admin; - - const [error, setError] = React.useState(null); - const [success, setSuccess] = React.useState(null); - - const updateFamily = async () => { - try { - setError(null); - setSuccess(null); - - await FamilyApi.UpdateFamily({ - id: family.family.family_id, - name: newName, - }); - - family.reloadFamilyInfo(); - - alert("Les paramètres de la famille ont été mis à jour avec succès !"); - } catch (e) { - console.error(e); - setError("Echec de la mise à jour des paramètres de la famille !"); - } - }; - const deleteFamily = async () => { try { if ( @@ -72,60 +51,8 @@ export function FamilySettingsRoute(): React.ReactElement { return ( <> - - {error && {error}} - {success && {success}} - - - - Paramètres de la famille - - - - - - - - setNewName(e.target.value)} - inputProps={{ - maxLength: ServerApi.Config.constraints.family_name_len.max, - }} - /> - - - - - - - + +
+ + + ); +} + +function FamilyExportCard(): React.ReactElement { + const loading = useLoadingMessage(); + const alert = useAlert(); + + const family = useFamily(); + + const [error, setError] = React.useState(); + const [success, setSuccess] = React.useState(); + + const exportData = async () => { + loading.show("Export des données"); + try { + setError(undefined); + setSuccess(undefined); + + const blob = await DataApi.ExportData(family.familyId); + downloadBlob(blob, `Export-${new Date().getTime()}.zip`); + } catch (e) { + console.error(e); + setError("Echec de l'export des données de la famille !"); + } + loading.hide(); + }; + + return ( + + + + Export / import des données de la famille + +

+ Vous pouvez, à des fins de sauvegardes ou de transfert, exporter et + importer l'ensemble des données des membres et des couples de cette + famille, sous format ZIP. +

+ + + Attention ! La restauration des données de la famille provoque + préalablement l'effacement de toutes les données enregistrées dans la + famille ! Par ailleurs, la restauration n'est pas réversible ! + + +

 

+ + +
+
+ ); +} diff --git a/geneit_app/src/utils/blob_utils.ts b/geneit_app/src/utils/blob_utils.ts new file mode 100644 index 0000000..80e9874 --- /dev/null +++ b/geneit_app/src/utils/blob_utils.ts @@ -0,0 +1,10 @@ +export async function downloadBlob(blob: Blob, filename: string) { + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.target = "_blank"; + link.rel = "noopener"; + link.download = filename; + link.click(); +} diff --git a/geneit_app/src/widgets/BaseFamilyRoute.tsx b/geneit_app/src/widgets/BaseFamilyRoute.tsx index c996ee3..c6639e6 100644 --- a/geneit_app/src/widgets/BaseFamilyRoute.tsx +++ b/geneit_app/src/widgets/BaseFamilyRoute.tsx @@ -141,6 +141,10 @@ export function BaseFamilyRoute(): React.ReactElement { backgroundColor: "background.paper", }} > + + Famille {family?.name} + + } label="Accueil" uri="" /> +): React.ReactElement { + return ( + + {p.error && {p.error}} + {p.success && {p.success}} + + {p.children} + + ); +}