diff --git a/geneit_app/src/api/DataApi.ts b/geneit_app/src/api/DataApi.ts index 9916cfd..2b9a735 100644 --- a/geneit_app/src/api/DataApi.ts +++ b/geneit_app/src/api/DataApi.ts @@ -14,4 +14,18 @@ export class DataApi { }); return res.data; } + + /** + * Import the data of a family + */ + static async ImportData(family_id: number, archive: Blob): Promise { + const fd = new FormData(); + fd.append("archive", archive); + const res = await APIClient.exec({ + uri: `/family/${family_id}/data/import`, + method: "PUT", + formData: fd, + }); + return res.data; + } } diff --git a/geneit_app/src/routes/family/FamilySettingsRoute.tsx b/geneit_app/src/routes/family/FamilySettingsRoute.tsx index c761d5a..da0d328 100644 --- a/geneit_app/src/routes/family/FamilySettingsRoute.tsx +++ b/geneit_app/src/routes/family/FamilySettingsRoute.tsx @@ -1,8 +1,9 @@ +import DownloadIcon from "@mui/icons-material/Download"; +import UploadIcon from "@mui/icons-material/Upload"; import { Alert, Box, Button, - Card, CardActions, CardContent, TextField, @@ -10,17 +11,16 @@ import { } from "@mui/material"; import React from "react"; import { useNavigate } from "react-router-dom"; +import { DataApi } from "../../api/DataApi"; import { FamilyApi } from "../../api/FamilyApi"; import { ServerApi } from "../../api/ServerApi"; 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"; +import { downloadBlob, selectFileToUpload } from "../../utils/files_utils"; +import { useFamily } from "../../widgets/BaseFamilyRoute"; +import { FamilyCard } from "../../widgets/FamilyCard"; +import { formatDate } from "../../widgets/TimeWidget"; export function FamilySettingsRoute(): React.ReactElement { const alert = useAlert(); @@ -154,6 +154,7 @@ function FamilySettingsCard(): React.ReactElement { function FamilyExportCard(): React.ReactElement { const loading = useLoadingMessage(); + const confirm = useConfirm(); const alert = useAlert(); const family = useFamily(); @@ -169,6 +170,8 @@ function FamilyExportCard(): React.ReactElement { const blob = await DataApi.ExportData(family.familyId); downloadBlob(blob, `Export-${new Date().getTime()}.zip`); + + setSuccess("Export des données effectué avec succès !"); } catch (e) { console.error(e); setError("Echec de l'export des données de la famille !"); @@ -176,6 +179,35 @@ function FamilyExportCard(): React.ReactElement { loading.hide(); }; + const importData = async () => { + try { + if ( + !(await confirm( + "Attention ! Cette opération a pour effet d'effacer toutes les données existantes en base ! Voulez-vous vraiment poursuivre l'opération ?" + )) + ) + return; + + const file = await selectFileToUpload({ + allowedTypes: ["application/zip"], + }); + if (file === null) return; + + loading.show("Restauration des données de la famille en cours..."); + + await DataApi.ImportData(family.familyId, file); + + family.reloadFamilyInfo(); + + alert("Import des données de la famille effectué avec succès !"); + } catch (e) { + console.error(e); + setError(`Echec de l'import des données de la famille ! (${e})`); + } + + loading.hide(); + }; + return ( @@ -201,9 +233,23 @@ function FamilyExportCard(): React.ReactElement { variant="outlined" fullWidth onClick={exportData} + size={"large"} + style={{ marginBottom: "10px" }} > Exporter les données de la famille + + ); diff --git a/geneit_app/src/utils/blob_utils.ts b/geneit_app/src/utils/blob_utils.ts deleted file mode 100644 index 80e9874..0000000 --- a/geneit_app/src/utils/blob_utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -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/utils/files_utils.ts b/geneit_app/src/utils/files_utils.ts new file mode 100644 index 0000000..f05591e --- /dev/null +++ b/geneit_app/src/utils/files_utils.ts @@ -0,0 +1,45 @@ +import { filesize } from "filesize"; + +/** + * Select a file to upload + */ +export async function selectFileToUpload(p: { + allowedTypes: string[]; + maxSize?: number; +}): Promise { + // Create file element + const fileEl = document.createElement("input"); + fileEl.type = "file"; + if (p.allowedTypes) fileEl.accept = p.allowedTypes.join(","); + fileEl.click(); + + // Wait for a file to be chosen + await new Promise((res, _rej) => + fileEl.addEventListener("change", () => res(null)) + ); + + if ((fileEl.files?.length ?? 0) === 0) return null; + const file = fileEl.files![0]; + + // Check file size + if (p.maxSize && file.size > p.maxSize) { + throw new Error( + `Le fichier sélectionné est trop lourd ! (taille maximale acceptée : ${filesize( + p.maxSize + )})` + ); + } + + return file; +} + +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/forms/UploadPhotoButton.tsx b/geneit_app/src/widgets/forms/UploadPhotoButton.tsx index 7185aa8..ba0559b 100644 --- a/geneit_app/src/widgets/forms/UploadPhotoButton.tsx +++ b/geneit_app/src/widgets/forms/UploadPhotoButton.tsx @@ -9,6 +9,7 @@ import getCroppedImg from "../../utils/crop_image"; import UploadIcon from "@mui/icons-material/Upload"; import LinkIcon from "@mui/icons-material/Link"; import { isDebug } from "../../utils/debug_utils"; +import { selectFileToUpload } from "../../utils/files_utils"; export function UploadPhotoButton(p: { label: string; @@ -22,39 +23,20 @@ export function UploadPhotoButton(p: { const uploadPhoto = async () => { try { - // Create file element - const fileEl = document.createElement("input"); - fileEl.type = "file"; - fileEl.accept = - ServerApi.Config.constraints.photo_allowed_types.join(","); - fileEl.click(); + const file = await selectFileToUpload({ + allowedTypes: ServerApi.Config.constraints.photo_allowed_types, + maxSize: ServerApi.Config.constraints.photo_max_size, + }); - // Wait for a file to be chosen - await new Promise((res, _rej) => - fileEl.addEventListener("change", () => res(null)) - ); + if (file === null) return; - if ((fileEl.files?.length ?? 0) === 0) return null; - const file = fileEl.files![0]; - - // Check file size - if (file.size > ServerApi.Config.constraints.photo_max_size) { - await alert( - `Le fichier sélectionné est trop lourd ! (taille maximale acceptée : ${filesize( - ServerApi.Config.constraints.photo_max_size - )})` - ); - return; - } - - const tempURL = URL.createObjectURL(fileEl.files![0]); + const tempURL = URL.createObjectURL(file); setImageBlob(file); setImageURL(tempURL); } catch (e) { console.error(e); - alert("Failed to upload custom account image!"); - return null; + alert(`Échec de l'envoi de l'image ! (${e})`); } };