Can export data from UI
This commit is contained in:
		
							
								
								
									
										17
									
								
								geneit_app/src/api/DataApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								geneit_app/src/api/DataApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<Blob> {
 | 
				
			||||||
 | 
					    const res = await APIClient.exec({
 | 
				
			||||||
 | 
					      uri: `/family/${family_id}/data/export`,
 | 
				
			||||||
 | 
					      method: "GET",
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return res.data;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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<LoadingMessageContext | null>(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 (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <LoadingMessageContextK.Provider value={hook}>
 | 
				
			||||||
 | 
					        {p.children}
 | 
				
			||||||
 | 
					      </LoadingMessageContextK.Provider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Dialog open={open}>
 | 
				
			||||||
 | 
					        <DialogContent>
 | 
				
			||||||
 | 
					          <DialogContentText>
 | 
				
			||||||
 | 
					            <div
 | 
				
			||||||
 | 
					              style={{
 | 
				
			||||||
 | 
					                display: "flex",
 | 
				
			||||||
 | 
					                alignItems: "center",
 | 
				
			||||||
 | 
					                justifyContent: "center",
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <CircularProgress style={{ marginRight: "15px" }} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              {message}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </DialogContentText>
 | 
				
			||||||
 | 
					        </DialogContent>
 | 
				
			||||||
 | 
					      </Dialog>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useLoadingMessage(): LoadingMessageContext {
 | 
				
			||||||
 | 
					  return React.useContext(LoadingMessageContextK)!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,6 +15,7 @@ import { ConfirmDialogProvider } from "./hooks/context_providers/ConfirmDialogPr
 | 
				
			|||||||
import { DarkThemeProvider } from "./hooks/context_providers/DarkThemeProvider";
 | 
					import { DarkThemeProvider } from "./hooks/context_providers/DarkThemeProvider";
 | 
				
			||||||
import { SnackbarProvider } from "./hooks/context_providers/SnackbarProvider";
 | 
					import { SnackbarProvider } from "./hooks/context_providers/SnackbarProvider";
 | 
				
			||||||
import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
				
			||||||
 | 
					import { LoadingMessageProvider } from "./hooks/context_providers/LoadingMessageProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function init() {
 | 
					async function init() {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@@ -28,14 +29,16 @@ async function init() {
 | 
				
			|||||||
          <AlertDialogProvider>
 | 
					          <AlertDialogProvider>
 | 
				
			||||||
            <ConfirmDialogProvider>
 | 
					            <ConfirmDialogProvider>
 | 
				
			||||||
              <SnackbarProvider>
 | 
					              <SnackbarProvider>
 | 
				
			||||||
                <div style={{ height: "100vh" }}>
 | 
					                <LoadingMessageProvider>
 | 
				
			||||||
                  <AsyncWidget
 | 
					                  <div style={{ height: "100vh" }}>
 | 
				
			||||||
                    loadKey={1}
 | 
					                    <AsyncWidget
 | 
				
			||||||
                    load={async () => await ServerApi.LoadConfig()}
 | 
					                      loadKey={1}
 | 
				
			||||||
                    errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !"
 | 
					                      load={async () => await ServerApi.LoadConfig()}
 | 
				
			||||||
                    build={() => <App />}
 | 
					                      errMsg="Echec de la connexion au serveur pour la récupération de la configuration statique !"
 | 
				
			||||||
                  />
 | 
					                      build={() => <App />}
 | 
				
			||||||
                </div>
 | 
					                    />
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </LoadingMessageProvider>
 | 
				
			||||||
              </SnackbarProvider>
 | 
					              </SnackbarProvider>
 | 
				
			||||||
            </ConfirmDialogProvider>
 | 
					            </ConfirmDialogProvider>
 | 
				
			||||||
          </AlertDialogProvider>
 | 
					          </AlertDialogProvider>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,11 @@ 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 { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
				
			||||||
import { formatDate } from "../../widgets/TimeWidget";
 | 
					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 {
 | 
					export function FamilySettingsRoute(): React.ReactElement {
 | 
				
			||||||
  const alert = useAlert();
 | 
					  const alert = useAlert();
 | 
				
			||||||
@@ -24,32 +29,6 @@ export function FamilySettingsRoute(): React.ReactElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const family = useFamily();
 | 
					  const family = useFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [newName, setNewName] = React.useState(family.family.name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const canEdit = family.family.is_admin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [error, setError] = React.useState<string | null>(null);
 | 
					 | 
				
			||||||
  const [success, setSuccess] = React.useState<string | null>(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 () => {
 | 
					  const deleteFamily = async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (
 | 
					      if (
 | 
				
			||||||
@@ -72,60 +51,8 @@ export function FamilySettingsRoute(): React.ReactElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Card style={{ margin: "10px auto", maxWidth: "450px" }}>
 | 
					      <FamilySettingsCard />
 | 
				
			||||||
        {error && <Alert severity="error">{error}</Alert>}
 | 
					      <FamilyExportCard />
 | 
				
			||||||
        {success && <Alert severity="success">{success}</Alert>}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <CardContent>
 | 
					 | 
				
			||||||
          <Typography gutterBottom variant="h5" component="div">
 | 
					 | 
				
			||||||
            Paramètres de la famille
 | 
					 | 
				
			||||||
          </Typography>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <Box
 | 
					 | 
				
			||||||
            component="form"
 | 
					 | 
				
			||||||
            sx={{
 | 
					 | 
				
			||||||
              "& .MuiTextField-root": { my: 1 },
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
            noValidate
 | 
					 | 
				
			||||||
            autoComplete="off"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <TextField
 | 
					 | 
				
			||||||
              disabled
 | 
					 | 
				
			||||||
              fullWidth
 | 
					 | 
				
			||||||
              label="Identifiant"
 | 
					 | 
				
			||||||
              value={family.family.family_id}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <TextField
 | 
					 | 
				
			||||||
              disabled
 | 
					 | 
				
			||||||
              fullWidth
 | 
					 | 
				
			||||||
              label="Création de la famille"
 | 
					 | 
				
			||||||
              value={formatDate(family.family.time_create)}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <TextField
 | 
					 | 
				
			||||||
              fullWidth
 | 
					 | 
				
			||||||
              label="Nom de la famille"
 | 
					 | 
				
			||||||
              value={newName}
 | 
					 | 
				
			||||||
              disabled={!canEdit}
 | 
					 | 
				
			||||||
              onChange={(e) => setNewName(e.target.value)}
 | 
					 | 
				
			||||||
              inputProps={{
 | 
					 | 
				
			||||||
                maxLength: ServerApi.Config.constraints.family_name_len.max,
 | 
					 | 
				
			||||||
              }}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </Box>
 | 
					 | 
				
			||||||
        </CardContent>
 | 
					 | 
				
			||||||
        <CardActions>
 | 
					 | 
				
			||||||
          <Button
 | 
					 | 
				
			||||||
            onClick={updateFamily}
 | 
					 | 
				
			||||||
            disabled={!canEdit}
 | 
					 | 
				
			||||||
            style={{ marginLeft: "auto" }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            Enregistrer
 | 
					 | 
				
			||||||
          </Button>
 | 
					 | 
				
			||||||
        </CardActions>
 | 
					 | 
				
			||||||
      </Card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div style={{ textAlign: "center", marginTop: "50px" }}>
 | 
					      <div style={{ textAlign: "center", marginTop: "50px" }}>
 | 
				
			||||||
        <Button
 | 
					        <Button
 | 
				
			||||||
          size="small"
 | 
					          size="small"
 | 
				
			||||||
@@ -139,3 +66,145 @@ export function FamilySettingsRoute(): React.ReactElement {
 | 
				
			|||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function FamilySettingsCard(): React.ReactElement {
 | 
				
			||||||
 | 
					  const alert = useAlert();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const family = useFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [newName, setNewName] = React.useState(family.family.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const canEdit = family.family.is_admin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [error, setError] = React.useState<string>();
 | 
				
			||||||
 | 
					  const [success, setSuccess] = React.useState<string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const updateFamily = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      setError(undefined);
 | 
				
			||||||
 | 
					      setSuccess(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      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 !");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <FamilyCard error={error} success={success}>
 | 
				
			||||||
 | 
					      <CardContent>
 | 
				
			||||||
 | 
					        <Typography gutterBottom variant="h5" component="div">
 | 
				
			||||||
 | 
					          Paramètres de la famille
 | 
				
			||||||
 | 
					        </Typography>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Box
 | 
				
			||||||
 | 
					          component="form"
 | 
				
			||||||
 | 
					          sx={{
 | 
				
			||||||
 | 
					            "& .MuiTextField-root": { my: 1 },
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          noValidate
 | 
				
			||||||
 | 
					          autoComplete="off"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <TextField
 | 
				
			||||||
 | 
					            disabled
 | 
				
			||||||
 | 
					            fullWidth
 | 
				
			||||||
 | 
					            label="Identifiant"
 | 
				
			||||||
 | 
					            value={family.family.family_id}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <TextField
 | 
				
			||||||
 | 
					            disabled
 | 
				
			||||||
 | 
					            fullWidth
 | 
				
			||||||
 | 
					            label="Création de la famille"
 | 
				
			||||||
 | 
					            value={formatDate(family.family.time_create)}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <TextField
 | 
				
			||||||
 | 
					            fullWidth
 | 
				
			||||||
 | 
					            label="Nom de la famille"
 | 
				
			||||||
 | 
					            value={newName}
 | 
				
			||||||
 | 
					            disabled={!canEdit}
 | 
				
			||||||
 | 
					            onChange={(e) => setNewName(e.target.value)}
 | 
				
			||||||
 | 
					            inputProps={{
 | 
				
			||||||
 | 
					              maxLength: ServerApi.Config.constraints.family_name_len.max,
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </Box>
 | 
				
			||||||
 | 
					      </CardContent>
 | 
				
			||||||
 | 
					      <CardActions>
 | 
				
			||||||
 | 
					        <Button
 | 
				
			||||||
 | 
					          onClick={updateFamily}
 | 
				
			||||||
 | 
					          disabled={!canEdit}
 | 
				
			||||||
 | 
					          style={{ marginLeft: "auto" }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Enregistrer
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </CardActions>
 | 
				
			||||||
 | 
					    </FamilyCard>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function FamilyExportCard(): React.ReactElement {
 | 
				
			||||||
 | 
					  const loading = useLoadingMessage();
 | 
				
			||||||
 | 
					  const alert = useAlert();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const family = useFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [error, setError] = React.useState<string>();
 | 
				
			||||||
 | 
					  const [success, setSuccess] = React.useState<string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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 (
 | 
				
			||||||
 | 
					    <FamilyCard error={error} success={success}>
 | 
				
			||||||
 | 
					      <CardContent>
 | 
				
			||||||
 | 
					        <Typography gutterBottom variant="h5" component="div">
 | 
				
			||||||
 | 
					          Export / import des données de la famille
 | 
				
			||||||
 | 
					        </Typography>
 | 
				
			||||||
 | 
					        <p>
 | 
				
			||||||
 | 
					          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.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Alert severity="warning">
 | 
				
			||||||
 | 
					          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 !
 | 
				
			||||||
 | 
					        </Alert>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <p> </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Button
 | 
				
			||||||
 | 
					          startIcon={<DownloadIcon />}
 | 
				
			||||||
 | 
					          variant="outlined"
 | 
				
			||||||
 | 
					          fullWidth
 | 
				
			||||||
 | 
					          onClick={exportData}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Exporter les données de la famille
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </CardContent>
 | 
				
			||||||
 | 
					    </FamilyCard>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								geneit_app/src/utils/blob_utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								geneit_app/src/utils/blob_utils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -141,6 +141,10 @@ export function BaseFamilyRoute(): React.ReactElement {
 | 
				
			|||||||
                  backgroundColor: "background.paper",
 | 
					                  backgroundColor: "background.paper",
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
 | 
					                <ListSubheader component="div">
 | 
				
			||||||
 | 
					                  Famille <em>{family?.name}</em>
 | 
				
			||||||
 | 
					                </ListSubheader>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <FamilyLink icon={<HomeIcon />} label="Accueil" uri="" />
 | 
					                <FamilyLink icon={<HomeIcon />} label="Accueil" uri="" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <FamilyLink
 | 
					                <FamilyLink
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								geneit_app/src/widgets/FamilyCard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								geneit_app/src/widgets/FamilyCard.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { Alert, Card } from "@mui/material";
 | 
				
			||||||
 | 
					import { PropsWithChildren } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function FamilyCard(
 | 
				
			||||||
 | 
					  p: PropsWithChildren<{ error?: string; success?: string }>
 | 
				
			||||||
 | 
					): React.ReactElement {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Card style={{ margin: "10px auto", maxWidth: "450px" }}>
 | 
				
			||||||
 | 
					      {p.error && <Alert severity="error">{p.error}</Alert>}
 | 
				
			||||||
 | 
					      {p.success && <Alert severity="success">{p.success}</Alert>}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {p.children}
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user