Can disable couple photos #5
@ -46,7 +46,6 @@ quinn@qlik.example
|
|||||||
sim@qlik.example
|
sim@qlik.example
|
||||||
phillie@qlik.example
|
phillie@qlik.example
|
||||||
peta@qlik.example
|
peta@qlik.example
|
||||||
peta@qlik.example
|
|
||||||
sibylla@qlik.example
|
sibylla@qlik.example
|
||||||
evan@qlik.example
|
evan@qlik.example
|
||||||
franklin@qlik.example
|
franklin@qlik.example
|
||||||
|
@ -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 {
|
export enum JoinFamilyResult {
|
||||||
TooManyRequests,
|
TooManyRequests,
|
||||||
InvalidCode,
|
InvalidCode,
|
||||||
@ -152,13 +160,13 @@ export class FamilyApi {
|
|||||||
/**
|
/**
|
||||||
* Get information about a single family
|
* 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({
|
const res = await APIClient.exec({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
uri: `/family/${id}`,
|
uri: `/family/${id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Family(res.data);
|
return new ExtendedFamilyInfo(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,12 +230,14 @@ export class FamilyApi {
|
|||||||
static async UpdateFamily(settings: {
|
static async UpdateFamily(settings: {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
disable_couple_photos: boolean;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await APIClient.exec({
|
await APIClient.exec({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
uri: `/family/${settings.id}`,
|
uri: `/family/${settings.id}`,
|
||||||
jsonData: {
|
jsonData: {
|
||||||
name: settings.name,
|
name: settings.name,
|
||||||
|
disable_couple_photos: settings.disable_couple_photos,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -414,49 +414,52 @@ export function CouplePage(p: {
|
|||||||
</PropertiesBox>
|
</PropertiesBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Photo */}
|
{
|
||||||
<Grid item sm={12} md={6}>
|
/* Photo */ !family.family.disable_couple_photos && (
|
||||||
<PropertiesBox title="Photo">
|
<Grid item sm={12} md={6}>
|
||||||
<div style={{ textAlign: "center" }}>
|
<PropertiesBox title="Photo">
|
||||||
<CouplePhoto couple={couple} width={150} />
|
<div style={{ textAlign: "center" }}>
|
||||||
<br />
|
<CouplePhoto couple={couple} width={150} />
|
||||||
{p.editing ? (
|
<br />
|
||||||
<p>
|
{p.editing ? (
|
||||||
Veuillez enregistrer / annuler les modifications apportées à
|
<p>
|
||||||
la fiche avant de changer la photo du couple.
|
Veuillez enregistrer / annuler les modifications apportées
|
||||||
</p>
|
à la fiche avant de changer la photo du couple.
|
||||||
) : (
|
</p>
|
||||||
<>
|
) : (
|
||||||
<UploadPhotoButton
|
<>
|
||||||
label={couple.hasPhoto ? "Remplacer" : "Ajouter"}
|
<UploadPhotoButton
|
||||||
onPhotoSelected={uploadNewPhoto}
|
label={couple.hasPhoto ? "Remplacer" : "Ajouter"}
|
||||||
aspect={5 / 4}
|
onPhotoSelected={uploadNewPhoto}
|
||||||
/>{" "}
|
aspect={5 / 4}
|
||||||
{couple.hasPhoto && (
|
/>{" "}
|
||||||
<RouterLink to={couple.photoURL!} target="_blank">
|
{couple.hasPhoto && (
|
||||||
<Button
|
<RouterLink to={couple.photoURL!} target="_blank">
|
||||||
variant="outlined"
|
<Button
|
||||||
startIcon={<FileDownloadIcon />}
|
variant="outlined"
|
||||||
>
|
startIcon={<FileDownloadIcon />}
|
||||||
Télécharger
|
>
|
||||||
</Button>
|
Télécharger
|
||||||
</RouterLink>
|
</Button>
|
||||||
|
</RouterLink>
|
||||||
|
)}{" "}
|
||||||
|
{couple.hasPhoto && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<DeleteIcon />}
|
||||||
|
color="error"
|
||||||
|
onClick={deletePhoto}
|
||||||
|
>
|
||||||
|
Supprimer
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{couple.hasPhoto && (
|
</div>
|
||||||
<Button
|
</PropertiesBox>
|
||||||
variant="outlined"
|
</Grid>
|
||||||
startIcon={<DeleteIcon />}
|
)
|
||||||
color="error"
|
}
|
||||||
onClick={deletePhoto}
|
|
||||||
>
|
|
||||||
Supprimer
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}{" "}
|
|
||||||
</div>
|
|
||||||
</PropertiesBox>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{/* Children */}
|
{/* Children */}
|
||||||
{p.children && (
|
{p.children && (
|
||||||
|
@ -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 (
|
return (
|
||||||
<DataGrid
|
<DataGrid
|
||||||
style={{ flex: "1" }}
|
style={{ flex: "1" }}
|
||||||
@ -233,7 +236,7 @@ function CouplesTable(p: {
|
|||||||
autoPageSize
|
autoPageSize
|
||||||
getRowId={(c) => c.id}
|
getRowId={(c) => c.id}
|
||||||
onCellDoubleClick={(p) => {
|
onCellDoubleClick={(p) => {
|
||||||
let member;
|
/*let member;
|
||||||
if (p.field === "wife") member = family.members.get(p.row.wife);
|
if (p.field === "wife") member = family.members.get(p.row.wife);
|
||||||
else if (p.field === "husband")
|
else if (p.field === "husband")
|
||||||
member = family.members.get(p.row.husband);
|
member = family.members.get(p.row.husband);
|
||||||
@ -241,7 +244,7 @@ function CouplesTable(p: {
|
|||||||
if (member) {
|
if (member) {
|
||||||
n(family.family.memberURL(member));
|
n(family.family.memberURL(member));
|
||||||
return;
|
return;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
n(family.family.coupleURL(p.row));
|
n(family.family.coupleURL(p.row));
|
||||||
}}
|
}}
|
||||||
|
@ -6,7 +6,10 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
CardActions,
|
CardActions,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
TextField,
|
TextField,
|
||||||
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -73,6 +76,9 @@ function FamilySettingsCard(): React.ReactElement {
|
|||||||
const family = useFamily();
|
const family = useFamily();
|
||||||
|
|
||||||
const [newName, setNewName] = React.useState(family.family.name);
|
const [newName, setNewName] = React.useState(family.family.name);
|
||||||
|
const [disableCouplePhotos, setDisableCouplePhotos] = React.useState(
|
||||||
|
family.family.disable_couple_photos
|
||||||
|
);
|
||||||
|
|
||||||
const canEdit = family.family.is_admin;
|
const canEdit = family.family.is_admin;
|
||||||
|
|
||||||
@ -87,6 +93,7 @@ function FamilySettingsCard(): React.ReactElement {
|
|||||||
await FamilyApi.UpdateFamily({
|
await FamilyApi.UpdateFamily({
|
||||||
id: family.family.family_id,
|
id: family.family.family_id,
|
||||||
name: newName,
|
name: newName,
|
||||||
|
disable_couple_photos: disableCouplePhotos,
|
||||||
});
|
});
|
||||||
|
|
||||||
family.reloadFamilyInfo();
|
family.reloadFamilyInfo();
|
||||||
@ -137,6 +144,18 @@ function FamilySettingsCard(): React.ReactElement {
|
|||||||
maxLength: ServerApi.Config.constraints.family_name_len.max,
|
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>
|
</Box>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions>
|
<CardActions>
|
||||||
|
@ -26,17 +26,17 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Outlet, useLocation, useParams } from "react-router-dom";
|
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 { MemberApi, MembersList } from "../api/MemberApi";
|
||||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
||||||
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
|
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
|
||||||
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
||||||
import { AsyncWidget } from "./AsyncWidget";
|
import { AsyncWidget } from "./AsyncWidget";
|
||||||
import { RouterLink } from "./RouterLink";
|
import { RouterLink } from "./RouterLink";
|
||||||
import { CoupleApi, CouplesList } from "../api/CoupleApi";
|
|
||||||
|
|
||||||
interface FamilyContext {
|
interface FamilyContext {
|
||||||
family: Family;
|
family: ExtendedFamilyInfo;
|
||||||
members: MembersList;
|
members: MembersList;
|
||||||
couples: CouplesList;
|
couples: CouplesList;
|
||||||
familyId: number;
|
familyId: number;
|
||||||
@ -53,7 +53,7 @@ export function BaseFamilyRoute(): React.ReactElement {
|
|||||||
const alert = useAlert();
|
const alert = useAlert();
|
||||||
const confirm = useConfirm();
|
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 [members, setMembers] = React.useState<null | MembersList>(null);
|
||||||
const [couples, setCouples] = React.useState<null | CouplesList>(null);
|
const [couples, setCouples] = React.useState<null | CouplesList>(null);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Stack, TextField, Typography } from "@mui/material";
|
import { Stack, TextField, Typography } from "@mui/material";
|
||||||
import { NumberConstraint, ServerApi } from "../../api/ServerApi";
|
import { NumberConstraint, ServerApi } from "../../api/ServerApi";
|
||||||
import { DateValue, fmtDate } from "../../api/MemberApi";
|
import { DateValue, fmtDate } from "../../api/MemberApi";
|
||||||
|
import { PropEdit } from "./PropEdit";
|
||||||
|
|
||||||
export function DateInput(p: {
|
export function DateInput(p: {
|
||||||
id: string;
|
id: string;
|
||||||
@ -13,13 +14,7 @@ export function DateInput(p: {
|
|||||||
if (!p.value) return <></>;
|
if (!p.value) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography
|
<PropEdit editable={false} label={p.label} value={fmtDate(p.value!)} />
|
||||||
variant="body2"
|
|
||||||
display="block"
|
|
||||||
style={{ marginBottom: "15px" }}
|
|
||||||
>
|
|
||||||
{p.label} : {fmtDate(p.value!)}
|
|
||||||
</Typography>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
alter table families drop column disable_couple_photos;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE families add column disable_couple_photos boolean not null default false;
|
@ -1,7 +1,7 @@
|
|||||||
use crate::constants::{StaticConstraints, FAMILY_INVITATION_CODE_LEN};
|
use crate::constants::{StaticConstraints, FAMILY_INVITATION_CODE_LEN};
|
||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership};
|
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::login_token_service::LoginToken;
|
||||||
use crate::services::rate_limiter_service::RatedAction;
|
use crate::services::rate_limiter_service::RatedAction;
|
||||||
use crate::services::{families_service, rate_limiter_service};
|
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
|
/// Get the information of a single family
|
||||||
pub async fn single_info(f: FamilyInPath) -> HttpResult {
|
pub async fn single_info(f: FamilyInPath) -> HttpResult {
|
||||||
Ok(HttpResponse::Ok()
|
let membership = families_service::get_family_membership(f.family_id(), f.user_id()).await?;
|
||||||
.json(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
|
/// Attempt to leave a family
|
||||||
@ -91,6 +102,7 @@ pub async fn leave(f: FamilyInPath) -> HttpResult {
|
|||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct UpdateFamilyBody {
|
pub struct UpdateFamilyBody {
|
||||||
name: String,
|
name: String,
|
||||||
|
disable_couple_photos: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a family
|
/// Update a family
|
||||||
@ -107,6 +119,7 @@ pub async fn update(
|
|||||||
|
|
||||||
let mut family = families_service::get_by_id(f.family_id()).await?;
|
let mut family = families_service::get_by_id(f.family_id()).await?;
|
||||||
family.name = req.0.name;
|
family.name = req.0.name;
|
||||||
|
family.disable_couple_photos = req.0.disable_couple_photos;
|
||||||
families_service::update_family(&family).await?;
|
families_service::update_family(&family).await?;
|
||||||
|
|
||||||
log::info!("User {:?} updated family {:?}", f.user_id(), f.family_id());
|
log::info!("User {:?} updated family {:?}", f.user_id(), f.family_id());
|
||||||
|
@ -64,6 +64,7 @@ pub struct Family {
|
|||||||
pub time_create: i64,
|
pub time_create: i64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub invitation_code: String,
|
pub invitation_code: String,
|
||||||
|
pub disable_couple_photos: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Family {
|
impl Family {
|
||||||
|
@ -25,6 +25,7 @@ diesel::table! {
|
|||||||
time_create -> Int8,
|
time_create -> Int8,
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
invitation_code -> Varchar,
|
invitation_code -> Varchar,
|
||||||
|
disable_couple_photos -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +174,7 @@ pub async fn update_family(family: &Family) -> anyhow::Result<()> {
|
|||||||
.set((
|
.set((
|
||||||
families::dsl::name.eq(family.name.clone()),
|
families::dsl::name.eq(family.name.clone()),
|
||||||
families::dsl::invitation_code.eq(family.invitation_code.clone()),
|
families::dsl::invitation_code.eq(family.invitation_code.clone()),
|
||||||
|
families::dsl::disable_couple_photos.eq(family.disable_couple_photos),
|
||||||
))
|
))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
})?;
|
})?;
|
||||||
|
Loading…
Reference in New Issue
Block a user