Can import family data from UI

This commit is contained in:
Pierre HUBERT 2023-08-18 15:27:29 +02:00
parent 6c82104cdc
commit 4b0292f0a4
5 changed files with 120 additions and 43 deletions

View File

@ -14,4 +14,18 @@ export class DataApi {
}); });
return res.data; return res.data;
} }
/**
* Import the data of a family
*/
static async ImportData(family_id: number, archive: Blob): Promise<Blob> {
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;
}
} }

View File

@ -1,8 +1,9 @@
import DownloadIcon from "@mui/icons-material/Download";
import UploadIcon from "@mui/icons-material/Upload";
import { import {
Alert, Alert,
Box, Box,
Button, Button,
Card,
CardActions, CardActions,
CardContent, CardContent,
TextField, TextField,
@ -10,17 +11,16 @@ import {
} from "@mui/material"; } from "@mui/material";
import React from "react"; import React from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { DataApi } from "../../api/DataApi";
import { FamilyApi } from "../../api/FamilyApi"; import { FamilyApi } from "../../api/FamilyApi";
import { ServerApi } from "../../api/ServerApi"; import { ServerApi } from "../../api/ServerApi";
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider"; import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider"; 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 { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider";
import { DataApi } from "../../api/DataApi"; import { downloadBlob, selectFileToUpload } from "../../utils/files_utils";
import { downloadBlob } from "../../utils/blob_utils"; import { useFamily } from "../../widgets/BaseFamilyRoute";
import { FamilyCard } from "../../widgets/FamilyCard";
import { formatDate } from "../../widgets/TimeWidget";
export function FamilySettingsRoute(): React.ReactElement { export function FamilySettingsRoute(): React.ReactElement {
const alert = useAlert(); const alert = useAlert();
@ -154,6 +154,7 @@ function FamilySettingsCard(): React.ReactElement {
function FamilyExportCard(): React.ReactElement { function FamilyExportCard(): React.ReactElement {
const loading = useLoadingMessage(); const loading = useLoadingMessage();
const confirm = useConfirm();
const alert = useAlert(); const alert = useAlert();
const family = useFamily(); const family = useFamily();
@ -169,6 +170,8 @@ function FamilyExportCard(): React.ReactElement {
const blob = await DataApi.ExportData(family.familyId); const blob = await DataApi.ExportData(family.familyId);
downloadBlob(blob, `Export-${new Date().getTime()}.zip`); downloadBlob(blob, `Export-${new Date().getTime()}.zip`);
setSuccess("Export des données effectué avec succès !");
} catch (e) { } catch (e) {
console.error(e); console.error(e);
setError("Echec de l'export des données de la famille !"); setError("Echec de l'export des données de la famille !");
@ -176,6 +179,35 @@ function FamilyExportCard(): React.ReactElement {
loading.hide(); 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 ( return (
<FamilyCard error={error} success={success}> <FamilyCard error={error} success={success}>
<CardContent> <CardContent>
@ -201,9 +233,23 @@ function FamilyExportCard(): React.ReactElement {
variant="outlined" variant="outlined"
fullWidth fullWidth
onClick={exportData} onClick={exportData}
size={"large"}
style={{ marginBottom: "10px" }}
> >
Exporter les données de la famille Exporter les données de la famille
</Button> </Button>
<Button
startIcon={<UploadIcon />}
variant="outlined"
color="warning"
fullWidth
onClick={importData}
disabled={!family.family.is_admin}
size={"large"}
>
Importer les données de la famille
</Button>
</CardContent> </CardContent>
</FamilyCard> </FamilyCard>
); );

View File

@ -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();
}

View File

@ -0,0 +1,45 @@
import { filesize } from "filesize";
/**
* Select a file to upload
*/
export async function selectFileToUpload(p: {
allowedTypes: string[];
maxSize?: number;
}): Promise<Blob | null> {
// 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();
}

View File

@ -9,6 +9,7 @@ import getCroppedImg from "../../utils/crop_image";
import UploadIcon from "@mui/icons-material/Upload"; import UploadIcon from "@mui/icons-material/Upload";
import LinkIcon from "@mui/icons-material/Link"; import LinkIcon from "@mui/icons-material/Link";
import { isDebug } from "../../utils/debug_utils"; import { isDebug } from "../../utils/debug_utils";
import { selectFileToUpload } from "../../utils/files_utils";
export function UploadPhotoButton(p: { export function UploadPhotoButton(p: {
label: string; label: string;
@ -22,39 +23,20 @@ export function UploadPhotoButton(p: {
const uploadPhoto = async () => { const uploadPhoto = async () => {
try { try {
// Create file element const file = await selectFileToUpload({
const fileEl = document.createElement("input"); allowedTypes: ServerApi.Config.constraints.photo_allowed_types,
fileEl.type = "file"; maxSize: ServerApi.Config.constraints.photo_max_size,
fileEl.accept = });
ServerApi.Config.constraints.photo_allowed_types.join(",");
fileEl.click();
// Wait for a file to be chosen if (file === null) return;
await new Promise((res, _rej) =>
fileEl.addEventListener("change", () => res(null))
);
if ((fileEl.files?.length ?? 0) === 0) return null; const tempURL = URL.createObjectURL(file);
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]);
setImageBlob(file); setImageBlob(file);
setImageURL(tempURL); setImageURL(tempURL);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
alert("Failed to upload custom account image!"); alert(`Échec de l'envoi de l'image ! (${e})`);
return null;
} }
}; };