4 Commits

Author SHA1 Message Date
d2a4bfb8e8 Add genealogy setting
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-05-15 19:05:45 +02:00
88e40fece6 Add a column in family table to toggle genealogy feature 2024-05-15 18:56:18 +02:00
c6b518d8de Fix icons alignment 2024-05-15 18:40:00 +02:00
cd7462ffb1 Refactor API routes
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-05-15 18:27:48 +02:00
26 changed files with 132 additions and 76 deletions

View File

@@ -1,6 +1,6 @@
import { APIClient } from "./ApiClient";
import { Couple } from "./CoupleApi";
import { Member } from "./MemberApi";
import { Member } from "./genealogy/MemberApi";
interface FamilyAPI {
user_id: number;
@@ -85,9 +85,11 @@ export class Family implements FamilyAPI {
export class ExtendedFamilyInfo extends Family {
public disable_couple_photos: boolean;
public enable_genealogy: boolean;
constructor(p: any) {
super(p);
this.disable_couple_photos = p.disable_couple_photos;
this.enable_genealogy = p.enable_genealogy;
}
}
@@ -230,6 +232,7 @@ export class FamilyApi {
static async UpdateFamily(settings: {
id: number;
name: string;
enable_genealogy: boolean;
disable_couple_photos: boolean;
}): Promise<void> {
await APIClient.exec({
@@ -237,6 +240,7 @@ export class FamilyApi {
uri: `/family/${settings.id}`,
jsonData: {
name: settings.name,
enable_genealogy: settings.enable_genealogy,
disable_couple_photos: settings.disable_couple_photos,
},
});

View File

@@ -1,6 +1,6 @@
import { APIClient } from "./ApiClient";
import { APIClient } from "../ApiClient";
import { DateValue, Member } from "./MemberApi";
import { ServerApi } from "./ServerApi";
import { ServerApi } from "../ServerApi";
interface CoupleApiInterface {
id: number;
@@ -161,7 +161,7 @@ export class CoupleApi {
*/
static async Create(m: Couple): Promise<Couple> {
const res = await APIClient.exec({
uri: `/family/${m.family_id}/couple/create`,
uri: `/family/${m.family_id}/genealogy/couple/create`,
method: "POST",
jsonData: m,
});
@@ -177,7 +177,7 @@ export class CoupleApi {
couple_id: number
): Promise<Couple> {
const res = await APIClient.exec({
uri: `/family/${family_id}/couple/${couple_id}`,
uri: `/family/${family_id}/genealogy/couple/${couple_id}`,
method: "GET",
});
@@ -189,7 +189,7 @@ export class CoupleApi {
*/
static async GetEntireList(family_id: number): Promise<CouplesList> {
const res = await APIClient.exec({
uri: `/family/${family_id}/couples`,
uri: `/family/${family_id}/genealogy/couples`,
method: "GET",
});
@@ -201,7 +201,7 @@ export class CoupleApi {
*/
static async Update(m: Couple): Promise<void> {
await APIClient.exec({
uri: `/family/${m.family_id}/couple/${m.id}`,
uri: `/family/${m.family_id}/genealogy/couple/${m.id}`,
method: "PUT",
jsonData: m,
});
@@ -214,7 +214,7 @@ export class CoupleApi {
const fd = new FormData();
fd.append("photo", b);
await APIClient.exec({
uri: `/family/${m.family_id}/couple/${m.id}/photo`,
uri: `/family/${m.family_id}/genealogy/couple/${m.id}/photo`,
method: "PUT",
formData: fd,
});
@@ -225,7 +225,7 @@ export class CoupleApi {
*/
static async RemoveCouplePhoto(m: Couple): Promise<void> {
await APIClient.exec({
uri: `/family/${m.family_id}/couple/${m.id}/photo`,
uri: `/family/${m.family_id}/genealogy/couple/${m.id}/photo`,
method: "DELETE",
});
}
@@ -235,7 +235,7 @@ export class CoupleApi {
*/
static async Delete(m: Couple): Promise<void> {
await APIClient.exec({
uri: `/family/${m.family_id}/couple/${m.id}`,
uri: `/family/${m.family_id}/genealogy/couple/${m.id}`,
method: "DELETE",
});
}

View File

@@ -1,4 +1,4 @@
import { APIClient } from "./ApiClient";
import { APIClient } from "../ApiClient";
/**
* Data management api client
@@ -9,7 +9,7 @@ export class DataApi {
*/
static async ExportData(family_id: number): Promise<Blob> {
const res = await APIClient.exec({
uri: `/family/${family_id}/data/export`,
uri: `/family/${family_id}/genealogy/data/export`,
method: "GET",
});
return res.data;
@@ -22,7 +22,7 @@ export class DataApi {
const fd = new FormData();
fd.append("archive", archive);
const res = await APIClient.exec({
uri: `/family/${family_id}/data/import`,
uri: `/family/${family_id}/genealogy/data/import`,
method: "PUT",
formData: fd,
});

View File

@@ -1,4 +1,4 @@
import { APIClient } from "./ApiClient";
import { APIClient } from "../ApiClient";
import { Couple } from "./CoupleApi";
export type Sex = "M" | "F";
@@ -278,7 +278,7 @@ export class MemberApi {
*/
static async Create(m: Member): Promise<Member> {
const res = await APIClient.exec({
uri: `/family/${m.family_id}/member/create`,
uri: `/family/${m.family_id}/genealogy/member/create`,
method: "POST",
jsonData: m,
});
@@ -294,7 +294,7 @@ export class MemberApi {
member_id: number
): Promise<Member> {
const res = await APIClient.exec({
uri: `/family/${family_id}/member/${member_id}`,
uri: `/family/${family_id}/genealogy/member/${member_id}`,
method: "GET",
});
@@ -306,7 +306,7 @@ export class MemberApi {
*/
static async GetEntireList(family_id: number): Promise<MembersList> {
const res = await APIClient.exec({
uri: `/family/${family_id}/members`,
uri: `/family/${family_id}/genealogy/members`,
method: "GET",
});
@@ -318,7 +318,7 @@ export class MemberApi {
*/
static async Update(m: Member): Promise<void> {
await APIClient.exec({
uri: `/family/${m.family_id}/member/${m.id}`,
uri: `/family/${m.family_id}/genealogy/member/${m.id}`,
method: "PUT",
jsonData: m,
});
@@ -331,7 +331,7 @@ export class MemberApi {
const fd = new FormData();
fd.append("photo", b);
await APIClient.exec({
uri: `/family/${m.family_id}/member/${m.id}/photo`,
uri: `/family/${m.family_id}/genealogy/member/${m.id}/photo`,
method: "PUT",
formData: fd,
});
@@ -342,7 +342,7 @@ export class MemberApi {
*/
static async RemoveMemberPhoto(m: Member): Promise<void> {
await APIClient.exec({
uri: `/family/${m.family_id}/member/${m.id}/photo`,
uri: `/family/${m.family_id}/genealogy/member/${m.id}/photo`,
method: "DELETE",
});
}
@@ -352,7 +352,7 @@ export class MemberApi {
*/
static async Delete(m: Member): Promise<void> {
await APIClient.exec({
uri: `/family/${m.family_id}/member/${m.id}`,
uri: `/family/${m.family_id}/genealogy/member/${m.id}`,
method: "DELETE",
});
}

View File

@@ -6,8 +6,8 @@ import SaveIcon from "@mui/icons-material/Save";
import { Button, Grid, Stack } from "@mui/material";
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Couple, CoupleApi } from "../../api/CoupleApi";
import { Member } from "../../api/MemberApi";
import { Couple, CoupleApi } from "../../api/genealogy/CoupleApi";
import { Member } from "../../api/genealogy/MemberApi";
import { ServerApi } from "../../api/ServerApi";
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";

View File

@@ -6,8 +6,8 @@ import { Button, TextField, Tooltip } from "@mui/material";
import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid";
import React from "react";
import { useNavigate } from "react-router-dom";
import { Couple, CoupleApi } from "../../api/CoupleApi";
import { dateTimestamp, fmtDate } from "../../api/MemberApi";
import { Couple, CoupleApi } from "../../api/genealogy/CoupleApi";
import { dateTimestamp, fmtDate } from "../../api/genealogy/MemberApi";
import { ServerApi } from "../../api/ServerApi";
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
@@ -132,7 +132,13 @@ function CouplesTable(p: {
sortable: false,
width: 60,
renderCell(params) {
return <CouplePhoto couple={params.row} />;
return (
<div
style={{ display: "flex", alignItems: "center", height: "100%" }}
>
<CouplePhoto couple={params.row} />
</div>
);
},
},

View File

@@ -14,8 +14,8 @@ import {
import * as EmailValidator from "email-validator";
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Couple } from "../../api/CoupleApi";
import { Member, MemberApi, fmtDate } from "../../api/MemberApi";
import { Couple } from "../../api/genealogy/CoupleApi";
import { Member, MemberApi, fmtDate } from "../../api/genealogy/MemberApi";
import { ServerApi } from "../../api/ServerApi";
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";

View File

@@ -8,7 +8,12 @@ import { Button, TextField, Tooltip, Typography } from "@mui/material";
import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid";
import React from "react";
import { useNavigate } from "react-router-dom";
import { Member, MemberApi, dateTimestamp, fmtDate } from "../../api/MemberApi";
import {
Member,
MemberApi,
dateTimestamp,
fmtDate,
} from "../../api/genealogy/MemberApi";
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
@@ -108,7 +113,13 @@ function MembersTable(p: {
sortable: false,
width: 60,
renderCell(params) {
return <MemberPhoto member={params.row} />;
return (
<div
style={{ display: "flex", alignItems: "center", height: "100%" }}
>
<MemberPhoto member={params.row} />
</div>
);
},
},

View File

@@ -14,7 +14,7 @@ import {
} from "@mui/material";
import React from "react";
import { useNavigate } from "react-router-dom";
import { DataApi } from "../../api/DataApi";
import { DataApi } from "../../api/genealogy/DataApi";
import { FamilyApi } from "../../api/FamilyApi";
import { ServerApi } from "../../api/ServerApi";
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
@@ -76,6 +76,9 @@ function FamilySettingsCard(): React.ReactElement {
const family = useFamily();
const [newName, setNewName] = React.useState(family.family.name);
const [enableGenealogy, setEnableGenealogy] = React.useState(
family.family.enable_genealogy
);
const [disableCouplePhotos, setDisableCouplePhotos] = React.useState(
family.family.disable_couple_photos
);
@@ -93,6 +96,7 @@ function FamilySettingsCard(): React.ReactElement {
await FamilyApi.UpdateFamily({
id: family.family.family_id,
name: newName,
enable_genealogy: enableGenealogy,
disable_couple_photos: disableCouplePhotos,
});
@@ -144,7 +148,22 @@ function FamilySettingsCard(): React.ReactElement {
maxLength: ServerApi.Config.constraints.family_name_len.max,
}}
/>
<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.">
<FormControlLabel
disabled={!canEdit}
control={
<Checkbox
checked={enableGenealogy}
onChange={(_e, c) => setEnableGenealogy(c)}
/>
}
label="Activer la 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={
@@ -156,6 +175,7 @@ function FamilySettingsCard(): React.ReactElement {
label="Désactiver les photos de couple"
/>
</Tooltip>
)}
</Box>
</CardContent>
<CardActions>

View File

@@ -1,5 +1,5 @@
import { Couple, CouplesList } from "../api/CoupleApi";
import { Member, MembersList, dateTimestamp } from "../api/MemberApi";
import { Couple, CouplesList } from "../api/genealogy/CoupleApi";
import { Member, MembersList, dateTimestamp } from "../api/genealogy/MemberApi";
export interface CoupleInformation {
couple: Couple;

View File

@@ -26,9 +26,9 @@ import {
} from "@mui/material";
import React from "react";
import { Outlet, useLocation, useParams } from "react-router-dom";
import { CoupleApi, CouplesList } from "../api/CoupleApi";
import { CoupleApi, CouplesList } from "../api/genealogy/CoupleApi";
import { ExtendedFamilyInfo, FamilyApi } from "../api/FamilyApi";
import { MemberApi, MembersList } from "../api/MemberApi";
import { MemberApi, MembersList } from "../api/genealogy/MemberApi";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";

View File

@@ -5,8 +5,8 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { TreeItem, SimpleTreeView } from "@mui/x-tree-view";
import React from "react";
import { useNavigate } from "react-router-dom";
import { Couple } from "../api/CoupleApi";
import { Member, fmtDate } from "../api/MemberApi";
import { Couple } from "../api/genealogy/CoupleApi";
import { Member, fmtDate } from "../api/genealogy/MemberApi";
import { FamilyTreeNode } from "../utils/family_tree";
import { useFamily } from "./BaseFamilyRoute";
import { MemberPhoto } from "./MemberPhoto";

View File

@@ -1,5 +1,5 @@
import { Avatar } from "@mui/material";
import { Couple } from "../api/CoupleApi";
import { Couple } from "../api/genealogy/CoupleApi";
export function CouplePhoto(p: {
couple: Couple;

View File

@@ -5,7 +5,7 @@ import {
ListItemSecondaryAction,
ListItemText,
} from "@mui/material";
import { Member, fmtDate } from "../api/MemberApi";
import { Member, fmtDate } from "../api/genealogy/MemberApi";
import { MemberPhoto } from "./MemberPhoto";
import Icon from "@mdi/react";
import FemaleIcon from "@mui/icons-material/Female";

View File

@@ -1,5 +1,5 @@
import { Avatar } from "@mui/material";
import { Member } from "../api/MemberApi";
import { Member } from "../api/genealogy/MemberApi";
export function MemberPhoto(p: {
member?: Member;

View File

@@ -1,6 +1,6 @@
import { Stack, TextField, Typography } from "@mui/material";
import { NumberConstraint, ServerApi } from "../../api/ServerApi";
import { DateValue, fmtDate } from "../../api/MemberApi";
import { DateValue, fmtDate } from "../../api/genealogy/MemberApi";
import { PropEdit } from "./PropEdit";
export function DateInput(p: {

View File

@@ -2,7 +2,7 @@ import ClearIcon from "@mui/icons-material/Clear";
import { Autocomplete, IconButton, TextField, Typography } from "@mui/material";
import React from "react";
import { useNavigate } from "react-router-dom";
import { Member } from "../../api/MemberApi";
import { Member } from "../../api/genealogy/MemberApi";
import { useFamily } from "../BaseFamilyRoute";
import { MemberItem } from "../MemberItem";

View File

@@ -6,7 +6,7 @@ import {
Radio,
Typography,
} from "@mui/material";
import { Sex } from "../../api/MemberApi";
import { Sex } from "../../api/genealogy/MemberApi";
export function SexSelection(p: {
readonly?: boolean;

View File

@@ -5,8 +5,8 @@ import { IconButton, Tooltip } from "@mui/material";
import jsPDF from "jspdf";
import React from "react";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { Couple } from "../../api/CoupleApi";
import { Member } from "../../api/MemberApi";
import { Couple } from "../../api/genealogy/CoupleApi";
import { Member } from "../../api/genealogy/MemberApi";
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
import { FamilyTreeNode } from "../../utils/family_tree";
import { downloadBlob } from "../../utils/files_utils";

View File

@@ -0,0 +1,3 @@
-- Remove column to toggle genealogy
ALTER TABLE public.families
DROP COLUMN enable_genealogy;

View File

@@ -0,0 +1,5 @@
-- Add column to toggle genealogy
ALTER TABLE public.families
ADD enable_genealogy boolean NOT NULL DEFAULT false;
COMMENT
ON COLUMN public.families.enable_genealogy IS 'Specify whether genealogy feature is enabled for the family';

View File

@@ -79,6 +79,7 @@ pub async fn list(token: LoginToken) -> HttpResult {
struct RichFamilyInfo {
#[serde(flatten)]
membership: FamilyMembership,
enable_genealogy: bool,
disable_couple_photos: bool,
}
@@ -88,6 +89,7 @@ pub async fn single_info(f: FamilyInPath) -> HttpResult {
let family = families_service::get_by_id(f.family_id()).await?;
Ok(HttpResponse::Ok().json(RichFamilyInfo {
membership,
enable_genealogy: family.enable_genealogy,
disable_couple_photos: family.disable_couple_photos,
}))
}
@@ -102,6 +104,7 @@ pub async fn leave(f: FamilyInPath) -> HttpResult {
#[derive(serde::Deserialize)]
pub struct UpdateFamilyBody {
name: String,
enable_genealogy: bool,
disable_couple_photos: bool,
}
@@ -119,6 +122,7 @@ pub async fn update(
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?;

View File

@@ -137,71 +137,71 @@ async fn main() -> std::io::Result<()> {
"/family/{id}/user/{user_id}",
web::delete().to(families_controller::delete_membership),
)
// Members controller
// [GENEALOGY] Members controller
.route(
"/family/{id}/member/create",
"/family/{id}/genealogy/member/create",
web::post().to(members_controller::create),
)
.route(
"/family/{id}/members",
"/family/{id}/genealogy/members",
web::get().to(members_controller::get_all),
)
.route(
"/family/{id}/member/{member_id}",
"/family/{id}/genealogy/member/{member_id}",
web::get().to(members_controller::get_single),
)
.route(
"/family/{id}/member/{member_id}",
"/family/{id}/genealogy/member/{member_id}",
web::put().to(members_controller::update),
)
.route(
"/family/{id}/member/{member_id}",
"/family/{id}/genealogy/member/{member_id}",
web::delete().to(members_controller::delete),
)
.route(
"/family/{id}/member/{member_id}/photo",
"/family/{id}/genealogy/member/{member_id}/photo",
web::put().to(members_controller::set_photo),
)
.route(
"/family/{id}/member/{member_id}/photo",
"/family/{id}/genealogy/member/{member_id}/photo",
web::delete().to(members_controller::remove_photo),
)
// Couples controller
// [GENEALOGY] Couples controller
.route(
"/family/{id}/couple/create",
"/family/{id}/genealogy/couple/create",
web::post().to(couples_controller::create),
)
.route(
"/family/{id}/couples",
"/family/{id}/genealogy/couples",
web::get().to(couples_controller::get_all),
)
.route(
"/family/{id}/couple/{couple_id}",
"/family/{id}/genealogy/couple/{couple_id}",
web::get().to(couples_controller::get_single),
)
.route(
"/family/{id}/couple/{couple_id}",
"/family/{id}/genealogy/couple/{couple_id}",
web::put().to(couples_controller::update),
)
.route(
"/family/{id}/couple/{couple_id}",
"/family/{id}/genealogy/couple/{couple_id}",
web::delete().to(couples_controller::delete),
)
.route(
"/family/{id}/couple/{couple_id}/photo",
"/family/{id}/genealogy/couple/{couple_id}/photo",
web::put().to(couples_controller::set_photo),
)
.route(
"/family/{id}/couple/{couple_id}/photo",
"/family/{id}/genealogy/couple/{couple_id}/photo",
web::delete().to(couples_controller::remove_photo),
)
// Data controller
// [GENEALOGY] Data controller
.route(
"/family/{id}/data/export",
"/family/{id}/genealogy/data/export",
web::get().to(data_controller::export_family),
)
.route(
"/family/{id}/data/import",
"/family/{id}/genealogy/data/import",
web::put().to(data_controller::import_family),
)
// Photos controller

View File

@@ -65,6 +65,7 @@ pub struct Family {
pub name: String,
pub invitation_code: String,
pub disable_couple_photos: bool,
pub enable_genealogy: bool,
}
impl Family {

View File

@@ -29,6 +29,7 @@ diesel::table! {
#[max_length = 7]
invitation_code -> Varchar,
disable_couple_photos -> Bool,
enable_genealogy -> Bool,
}
}

View File

@@ -174,6 +174,7 @@ pub async fn update_family(family: &Family) -> anyhow::Result<()> {
.set((
families::dsl::name.eq(family.name.clone()),
families::dsl::invitation_code.eq(family.invitation_code.clone()),
families::dsl::enable_genealogy.eq(family.enable_genealogy),
families::dsl::disable_couple_photos.eq(family.disable_couple_photos),
))
.execute(conn)