Can import family data from UI
This commit is contained in:
		@@ -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<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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 (
 | 
			
		||||
    <FamilyCard error={error} success={success}>
 | 
			
		||||
      <CardContent>
 | 
			
		||||
@@ -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
 | 
			
		||||
        </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>
 | 
			
		||||
    </FamilyCard>
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								geneit_app/src/utils/files_utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								geneit_app/src/utils/files_utils.ts
									
									
									
									
									
										Normal 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();
 | 
			
		||||
}
 | 
			
		||||
@@ -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})`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user