Separate genealogy settings from family settings
This commit is contained in:
parent
28b29d6cd6
commit
9c8b424759
@ -38,6 +38,7 @@ import { FamilyTreeRoute } from "./routes/family/genealogy/FamilyTreeRoute";
|
||||
import { FamilyMemberTreeRoute } from "./routes/family/genealogy/FamilyMemberTreeRoute";
|
||||
import { GenealogyHomeRoute } from "./routes/family/genealogy/GenealogyHomeRoute";
|
||||
import { BaseGenealogyRoute } from "./widgets/genealogy/BaseGenealogyRoute";
|
||||
import { GenalogySettingsRoute } from "./routes/family/genealogy/GenalogySettingsRoute";
|
||||
|
||||
interface AuthContext {
|
||||
signedIn: boolean;
|
||||
@ -105,6 +106,7 @@ export function App(): React.ReactElement {
|
||||
path="tree/:memberId"
|
||||
element={<FamilyMemberTreeRoute />}
|
||||
/>
|
||||
<Route path="settings" element={<GenalogySettingsRoute />} />
|
||||
<Route path="*" element={<NotFoundRoute />} />
|
||||
</Route>
|
||||
|
||||
|
@ -233,9 +233,9 @@ export class FamilyApi {
|
||||
*/
|
||||
static async UpdateFamily(settings: {
|
||||
id: number;
|
||||
name: string;
|
||||
enable_genealogy: boolean;
|
||||
disable_couple_photos: boolean;
|
||||
name?: string;
|
||||
enable_genealogy?: boolean;
|
||||
disable_couple_photos?: boolean;
|
||||
}): Promise<void> {
|
||||
await APIClient.exec({
|
||||
method: "PATCH",
|
||||
|
@ -1,26 +1,19 @@
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import UploadIcon from "@mui/icons-material/Upload";
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
CardActions,
|
||||
CardContent,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FamilyApi } from "../../api/FamilyApi";
|
||||
import { ServerApi } from "../../api/ServerApi";
|
||||
import { DataApi } from "../../api/genealogy/DataApi";
|
||||
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
|
||||
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
|
||||
import { useLoadingMessage } from "../../hooks/context_providers/LoadingMessageProvider";
|
||||
import { downloadBlob, selectFileToUpload } from "../../utils/files_utils";
|
||||
import { useFamily } from "../../widgets/BaseFamilyRoute";
|
||||
import { FamilyCard } from "../../widgets/FamilyCard";
|
||||
import { formatDate } from "../../widgets/TimeWidget";
|
||||
@ -55,7 +48,6 @@ export function FamilySettingsRoute(): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<FamilySettingsCard />
|
||||
{family.family.enable_genealogy && <GenealogyExportCard />}
|
||||
<div style={{ textAlign: "center", marginTop: "50px" }}>
|
||||
<Button
|
||||
size="small"
|
||||
@ -79,9 +71,6 @@ function FamilySettingsCard(): React.ReactElement {
|
||||
const [enableGenealogy, setEnableGenealogy] = React.useState(
|
||||
family.family.enable_genealogy
|
||||
);
|
||||
const [disableCouplePhotos, setDisableCouplePhotos] = React.useState(
|
||||
family.family.disable_couple_photos
|
||||
);
|
||||
|
||||
const canEdit = family.family.is_admin;
|
||||
|
||||
@ -97,7 +86,6 @@ function FamilySettingsCard(): React.ReactElement {
|
||||
id: family.family.family_id,
|
||||
name: newName,
|
||||
enable_genealogy: enableGenealogy,
|
||||
disable_couple_photos: disableCouplePhotos,
|
||||
});
|
||||
|
||||
family.reloadFamilyInfo();
|
||||
@ -152,30 +140,13 @@ function FamilySettingsCard(): React.ReactElement {
|
||||
<FormControlLabel
|
||||
disabled={!canEdit}
|
||||
control={
|
||||
<Checkbox
|
||||
<Switch
|
||||
checked={enableGenealogy}
|
||||
onChange={(_e, c) => setEnableGenealogy(c)}
|
||||
/>
|
||||
}
|
||||
label="Activer la généalogie"
|
||||
label="Activer le module de généalogie"
|
||||
/>
|
||||
{enableGenealogy && (
|
||||
<Tooltip
|
||||
title="Les photos de couple ne sont pas utilisées en pratique dans les arbres généalogiques. Il est possible de masquer les formulaires d'édition de photos de couple pour limiter le risque de confusion."
|
||||
arrow
|
||||
>
|
||||
<FormControlLabel
|
||||
disabled={!canEdit}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={disableCouplePhotos}
|
||||
onChange={(_e, c) => setDisableCouplePhotos(c)}
|
||||
/>
|
||||
}
|
||||
label="Désactiver les photos de couple"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
@ -190,109 +161,3 @@ function FamilySettingsCard(): React.ReactElement {
|
||||
</FamilyCard>
|
||||
);
|
||||
}
|
||||
|
||||
function GenealogyExportCard(): React.ReactElement {
|
||||
const loading = useLoadingMessage();
|
||||
const confirm = useConfirm();
|
||||
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`);
|
||||
|
||||
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 !");
|
||||
}
|
||||
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;
|
||||
|
||||
setError(undefined);
|
||||
setSuccess(undefined);
|
||||
|
||||
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>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
Export / import des données de généalogie
|
||||
</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}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
221
geneit_app/src/routes/family/genealogy/GenalogySettingsRoute.tsx
Normal file
221
geneit_app/src/routes/family/genealogy/GenalogySettingsRoute.tsx
Normal file
@ -0,0 +1,221 @@
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import UploadIcon from "@mui/icons-material/Upload";
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
CardActions,
|
||||
CardContent,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
import { FamilyApi } from "../../../api/FamilyApi";
|
||||
import { DataApi } from "../../../api/genealogy/DataApi";
|
||||
import { useAlert } from "../../../hooks/context_providers/AlertDialogProvider";
|
||||
import { useConfirm } from "../../../hooks/context_providers/ConfirmDialogProvider";
|
||||
import { useLoadingMessage } from "../../../hooks/context_providers/LoadingMessageProvider";
|
||||
import { downloadBlob, selectFileToUpload } from "../../../utils/files_utils";
|
||||
import { useFamily } from "../../../widgets/BaseFamilyRoute";
|
||||
import { FamilyCard } from "../../../widgets/FamilyCard";
|
||||
|
||||
export function GenalogySettingsRoute(): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<GenealogySettingsCard />
|
||||
<GenealogyExportCard />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function GenealogySettingsCard(): React.ReactElement {
|
||||
const alert = useAlert();
|
||||
|
||||
const family = useFamily();
|
||||
|
||||
const [disableCouplePhotos, setDisableCouplePhotos] = React.useState(
|
||||
family.family.disable_couple_photos
|
||||
);
|
||||
|
||||
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,
|
||||
disable_couple_photos: disableCouplePhotos,
|
||||
});
|
||||
|
||||
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 du module de généalogie
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
component="form"
|
||||
sx={{
|
||||
"& .MuiTextField-root": { my: 1 },
|
||||
}}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
<Tooltip
|
||||
title="Les photos de couple ne sont pas utilisées en pratique dans les arbres généalogiques. Il est possible de masquer les formulaires d'édition de photos de couple pour limiter le risque de confusion."
|
||||
arrow
|
||||
>
|
||||
<FormControlLabel
|
||||
disabled={!canEdit}
|
||||
control={
|
||||
<Switch
|
||||
checked={disableCouplePhotos}
|
||||
onChange={(_e, c) => setDisableCouplePhotos(c)}
|
||||
/>
|
||||
}
|
||||
label="Désactiver les photos de couple"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button
|
||||
onClick={updateFamily}
|
||||
disabled={!canEdit}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
Enregistrer
|
||||
</Button>
|
||||
</CardActions>
|
||||
</FamilyCard>
|
||||
);
|
||||
}
|
||||
|
||||
function GenealogyExportCard(): React.ReactElement {
|
||||
const loading = useLoadingMessage();
|
||||
const confirm = useConfirm();
|
||||
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`);
|
||||
|
||||
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 !");
|
||||
}
|
||||
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;
|
||||
|
||||
setError(undefined);
|
||||
setSuccess(undefined);
|
||||
|
||||
loading.show(
|
||||
"Restauration des données de généalogie de la famille en cours..."
|
||||
);
|
||||
|
||||
await DataApi.ImportData(family.familyId, file);
|
||||
|
||||
family.reloadFamilyInfo();
|
||||
|
||||
alert(
|
||||
"Import des données de généalogie de la famille effectué avec succès !"
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError(
|
||||
`Echec de l'import des données de généalogie de la famille ! (${e})`
|
||||
);
|
||||
}
|
||||
|
||||
loading.hide();
|
||||
};
|
||||
|
||||
return (
|
||||
<FamilyCard error={error} success={success}>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
Export / import des données de généalogie
|
||||
</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 généalogie 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}
|
||||
size={"large"}
|
||||
style={{ marginBottom: "10px" }}
|
||||
>
|
||||
Exporter les données de généalogie
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
startIcon={<UploadIcon />}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
fullWidth
|
||||
onClick={importData}
|
||||
disabled={!family.family.is_admin}
|
||||
size={"large"}
|
||||
>
|
||||
Importer les données de généalogie
|
||||
</Button>
|
||||
</CardContent>
|
||||
</FamilyCard>
|
||||
);
|
||||
}
|
@ -4,6 +4,7 @@ import {
|
||||
mdiContentCopy,
|
||||
mdiCrowd,
|
||||
mdiFamilyTree,
|
||||
mdiFileTree,
|
||||
mdiHumanMaleFemale,
|
||||
mdiLockCheck,
|
||||
mdiPlus,
|
||||
@ -190,6 +191,14 @@ export function BaseFamilyRoute(): React.ReactElement {
|
||||
uri="settings"
|
||||
/>
|
||||
|
||||
{family?.enable_genealogy && (
|
||||
<FamilyLink
|
||||
icon={<Icon path={mdiFileTree} size={1} />}
|
||||
label="Généalogie"
|
||||
uri="genealogy/settings"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Invitation code */}
|
||||
|
||||
<ListItem
|
||||
|
@ -103,9 +103,9 @@ pub async fn leave(f: FamilyInPath) -> HttpResult {
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct UpdateFamilyBody {
|
||||
name: String,
|
||||
enable_genealogy: bool,
|
||||
disable_couple_photos: bool,
|
||||
name: Option<String>,
|
||||
enable_genealogy: Option<bool>,
|
||||
disable_couple_photos: Option<bool>,
|
||||
}
|
||||
|
||||
/// Update a family
|
||||
@ -113,17 +113,24 @@ pub async fn update(
|
||||
f: FamilyInPathWithAdminMembership,
|
||||
req: web::Json<UpdateFamilyBody>,
|
||||
) -> HttpResult {
|
||||
if !StaticConstraints::default()
|
||||
.family_name_len
|
||||
.validate(&req.name)
|
||||
{
|
||||
return Ok(HttpResponse::BadRequest().body("Invalid family name!"));
|
||||
let mut family = families_service::get_by_id(f.family_id()).await?;
|
||||
|
||||
if let Some(name) = &req.name {
|
||||
if !StaticConstraints::default().family_name_len.validate(name) {
|
||||
return Ok(HttpResponse::BadRequest().body("Invalid family name!"));
|
||||
}
|
||||
|
||||
family.name = name.to_string();
|
||||
}
|
||||
|
||||
if let Some(enable_genealogy) = req.enable_genealogy {
|
||||
family.enable_genealogy = enable_genealogy;
|
||||
}
|
||||
|
||||
if let Some(disable_couple_photos) = req.disable_couple_photos {
|
||||
family.disable_couple_photos = disable_couple_photos;
|
||||
}
|
||||
|
||||
let mut family = families_service::get_by_id(f.family_id()).await?;
|
||||
family.name = req.0.name;
|
||||
family.enable_genealogy = req.0.enable_genealogy;
|
||||
family.disable_couple_photos = req.0.disable_couple_photos;
|
||||
families_service::update_family(&family).await?;
|
||||
|
||||
log::info!("User {:?} updated family {:?}", f.user_id(), f.family_id());
|
||||
|
Loading…
Reference in New Issue
Block a user