Can disable couple photos (#5)
All checks were successful
continuous-integration/drone/push Build is passing

Add an option in family settings to disable couple photos from Web UI

Reviewed-on: #5
This commit is contained in:
Pierre HUBERT 2023-08-26 14:55:23 +00:00
parent 8086c1b4c9
commit 137b7422cf
13 changed files with 110 additions and 61 deletions

View File

@ -46,7 +46,6 @@ quinn@qlik.example
sim@qlik.example
phillie@qlik.example
peta@qlik.example
peta@qlik.example
sibylla@qlik.example
evan@qlik.example
franklin@qlik.example

View File

@ -83,6 +83,14 @@ export class Family implements FamilyAPI {
}
}
export class ExtendedFamilyInfo extends Family {
public disable_couple_photos: boolean;
constructor(p: any) {
super(p);
this.disable_couple_photos = p.disable_couple_photos;
}
}
export enum JoinFamilyResult {
TooManyRequests,
InvalidCode,
@ -152,13 +160,13 @@ export class FamilyApi {
/**
* Get information about a single family
*/
static async GetSingle(id: number): Promise<Family> {
static async GetSingle(id: number): Promise<ExtendedFamilyInfo> {
const res = await APIClient.exec({
method: "GET",
uri: `/family/${id}`,
});
return new Family(res.data);
return new ExtendedFamilyInfo(res.data);
}
/**
@ -222,12 +230,14 @@ export class FamilyApi {
static async UpdateFamily(settings: {
id: number;
name: string;
disable_couple_photos: boolean;
}): Promise<void> {
await APIClient.exec({
method: "PATCH",
uri: `/family/${settings.id}`,
jsonData: {
name: settings.name,
disable_couple_photos: settings.disable_couple_photos,
},
});
}

View File

@ -414,49 +414,52 @@ export function CouplePage(p: {
</PropertiesBox>
</Grid>
{/* Photo */}
<Grid item sm={12} md={6}>
<PropertiesBox title="Photo">
<div style={{ textAlign: "center" }}>
<CouplePhoto couple={couple} width={150} />
<br />
{p.editing ? (
<p>
Veuillez enregistrer / annuler les modifications apportées à
la fiche avant de changer la photo du couple.
</p>
) : (
<>
<UploadPhotoButton
label={couple.hasPhoto ? "Remplacer" : "Ajouter"}
onPhotoSelected={uploadNewPhoto}
aspect={5 / 4}
/>{" "}
{couple.hasPhoto && (
<RouterLink to={couple.photoURL!} target="_blank">
<Button
variant="outlined"
startIcon={<FileDownloadIcon />}
>
Télécharger
</Button>
</RouterLink>
{
/* Photo */ !family.family.disable_couple_photos && (
<Grid item sm={12} md={6}>
<PropertiesBox title="Photo">
<div style={{ textAlign: "center" }}>
<CouplePhoto couple={couple} width={150} />
<br />
{p.editing ? (
<p>
Veuillez enregistrer / annuler les modifications apportées
à la fiche avant de changer la photo du couple.
</p>
) : (
<>
<UploadPhotoButton
label={couple.hasPhoto ? "Remplacer" : "Ajouter"}
onPhotoSelected={uploadNewPhoto}
aspect={5 / 4}
/>{" "}
{couple.hasPhoto && (
<RouterLink to={couple.photoURL!} target="_blank">
<Button
variant="outlined"
startIcon={<FileDownloadIcon />}
>
Télécharger
</Button>
</RouterLink>
)}{" "}
{couple.hasPhoto && (
<Button
variant="outlined"
startIcon={<DeleteIcon />}
color="error"
onClick={deletePhoto}
>
Supprimer
</Button>
)}
</>
)}{" "}
{couple.hasPhoto && (
<Button
variant="outlined"
startIcon={<DeleteIcon />}
color="error"
onClick={deletePhoto}
>
Supprimer
</Button>
)}
</>
)}{" "}
</div>
</PropertiesBox>
</Grid>
</div>
</PropertiesBox>
</Grid>
)
}
{/* Children */}
{p.children && (

View File

@ -225,6 +225,9 @@ function CouplesTable(p: {
},
];
// If couple photos are hidden, remove their column
if (family.family.disable_couple_photos) columns.splice(0, 1);
return (
<DataGrid
style={{ flex: "1" }}
@ -233,7 +236,7 @@ function CouplesTable(p: {
autoPageSize
getRowId={(c) => c.id}
onCellDoubleClick={(p) => {
let member;
/*let member;
if (p.field === "wife") member = family.members.get(p.row.wife);
else if (p.field === "husband")
member = family.members.get(p.row.husband);
@ -241,7 +244,7 @@ function CouplesTable(p: {
if (member) {
n(family.family.memberURL(member));
return;
}
}*/
n(family.family.coupleURL(p.row));
}}

View File

@ -6,7 +6,10 @@ import {
Button,
CardActions,
CardContent,
Checkbox,
FormControlLabel,
TextField,
Tooltip,
Typography,
} from "@mui/material";
import React from "react";
@ -73,6 +76,9 @@ function FamilySettingsCard(): React.ReactElement {
const family = useFamily();
const [newName, setNewName] = React.useState(family.family.name);
const [disableCouplePhotos, setDisableCouplePhotos] = React.useState(
family.family.disable_couple_photos
);
const canEdit = family.family.is_admin;
@ -87,6 +93,7 @@ function FamilySettingsCard(): React.ReactElement {
await FamilyApi.UpdateFamily({
id: family.family.family_id,
name: newName,
disable_couple_photos: disableCouplePhotos,
});
family.reloadFamilyInfo();
@ -137,6 +144,18 @@ 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={disableCouplePhotos}
onChange={(_e, c) => setDisableCouplePhotos(c)}
/>
}
label="Désactiver les photos de couple"
/>
</Tooltip>
</Box>
</CardContent>
<CardActions>

View File

@ -26,17 +26,17 @@ import {
} from "@mui/material";
import React from "react";
import { Outlet, useLocation, useParams } from "react-router-dom";
import { Family, FamilyApi } from "../api/FamilyApi";
import { CoupleApi, CouplesList } from "../api/CoupleApi";
import { ExtendedFamilyInfo, FamilyApi } from "../api/FamilyApi";
import { MemberApi, MembersList } from "../api/MemberApi";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
import { AsyncWidget } from "./AsyncWidget";
import { RouterLink } from "./RouterLink";
import { CoupleApi, CouplesList } from "../api/CoupleApi";
interface FamilyContext {
family: Family;
family: ExtendedFamilyInfo;
members: MembersList;
couples: CouplesList;
familyId: number;
@ -53,7 +53,7 @@ export function BaseFamilyRoute(): React.ReactElement {
const alert = useAlert();
const confirm = useConfirm();
const [family, setFamily] = React.useState<null | Family>(null);
const [family, setFamily] = React.useState<null | ExtendedFamilyInfo>(null);
const [members, setMembers] = React.useState<null | MembersList>(null);
const [couples, setCouples] = React.useState<null | CouplesList>(null);

View File

@ -1,6 +1,7 @@
import { Stack, TextField, Typography } from "@mui/material";
import { NumberConstraint, ServerApi } from "../../api/ServerApi";
import { DateValue, fmtDate } from "../../api/MemberApi";
import { PropEdit } from "./PropEdit";
export function DateInput(p: {
id: string;
@ -13,13 +14,7 @@ export function DateInput(p: {
if (!p.value) return <></>;
return (
<Typography
variant="body2"
display="block"
style={{ marginBottom: "15px" }}
>
{p.label} : {fmtDate(p.value!)}
</Typography>
<PropEdit editable={false} label={p.label} value={fmtDate(p.value!)} />
);
}

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
alter table families drop column disable_couple_photos;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE families add column disable_couple_photos boolean not null default false;

View File

@ -1,7 +1,7 @@
use crate::constants::{StaticConstraints, FAMILY_INVITATION_CODE_LEN};
use crate::controllers::HttpResult;
use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership};
use crate::models::UserID;
use crate::models::{FamilyMembership, UserID};
use crate::services::login_token_service::LoginToken;
use crate::services::rate_limiter_service::RatedAction;
use crate::services::{families_service, rate_limiter_service};
@ -75,10 +75,21 @@ pub async fn list(token: LoginToken) -> HttpResult {
)
}
#[derive(serde::Serialize)]
struct RichFamilyInfo {
#[serde(flatten)]
membership: FamilyMembership,
disable_couple_photos: bool,
}
/// Get the information of a single family
pub async fn single_info(f: FamilyInPath) -> HttpResult {
Ok(HttpResponse::Ok()
.json(families_service::get_family_membership(f.family_id(), f.user_id()).await?))
let membership = families_service::get_family_membership(f.family_id(), f.user_id()).await?;
let family = families_service::get_by_id(f.family_id()).await?;
Ok(HttpResponse::Ok().json(RichFamilyInfo {
membership,
disable_couple_photos: family.disable_couple_photos,
}))
}
/// Attempt to leave a family
@ -91,6 +102,7 @@ pub async fn leave(f: FamilyInPath) -> HttpResult {
#[derive(serde::Deserialize)]
pub struct UpdateFamilyBody {
name: String,
disable_couple_photos: bool,
}
/// Update a family
@ -107,6 +119,7 @@ pub async fn update(
let mut family = families_service::get_by_id(f.family_id()).await?;
family.name = req.0.name;
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());

View File

@ -64,6 +64,7 @@ pub struct Family {
pub time_create: i64,
pub name: String,
pub invitation_code: String,
pub disable_couple_photos: bool,
}
impl Family {

View File

@ -25,6 +25,7 @@ diesel::table! {
time_create -> Int8,
name -> Varchar,
invitation_code -> Varchar,
disable_couple_photos -> 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::disable_couple_photos.eq(family.disable_couple_photos),
))
.execute(conn)
})?;