Can set the father and the mother of a member
This commit is contained in:
@ -3,13 +3,17 @@ import { Member } from "../api/MemberApi";
|
||||
|
||||
export function MemberPhoto(p: {
|
||||
member: Member;
|
||||
width: number;
|
||||
width?: number;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<Avatar
|
||||
sx={{ width: `${p.width}px`, height: "auto", display: "inline-block" }}
|
||||
sx={
|
||||
p.width
|
||||
? { width: `${p.width}px`, height: "auto", display: "inline-block" }
|
||||
: undefined
|
||||
}
|
||||
variant="rounded"
|
||||
src={p.member.thumbnailURL}
|
||||
src={p.member.thumbnailURL ?? undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,16 @@
|
||||
import { Stack, TextField, Typography } from "@mui/material";
|
||||
import { NumberConstraint, ServerApi } from "../../api/ServerApi";
|
||||
|
||||
export interface DateValue {
|
||||
year?: number;
|
||||
month?: number;
|
||||
day?: number;
|
||||
}
|
||||
import { DateValue, fmtDate } from "../../api/MemberApi";
|
||||
|
||||
export function DateInput(p: {
|
||||
id: string;
|
||||
label: string;
|
||||
editable: boolean;
|
||||
value: DateValue;
|
||||
value?: DateValue;
|
||||
onValueChange: (newVal: DateValue) => void;
|
||||
}): React.ReactElement {
|
||||
if (!p.editable) {
|
||||
if (!p.value.year && !p.value.month && !p.value.day) return <></>;
|
||||
if (!p.value) return <></>;
|
||||
|
||||
return (
|
||||
<Typography
|
||||
@ -23,8 +18,7 @@ export function DateInput(p: {
|
||||
display="block"
|
||||
style={{ marginBottom: "15px" }}
|
||||
>
|
||||
{p.label} : {p.value.day ?? "__"} / {p.value.month ?? "__"} /{" "}
|
||||
{p.value.year ?? "__"}
|
||||
{p.label} : {fmtDate(p.value!)}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
@ -41,8 +35,8 @@ export function DateInput(p: {
|
||||
required
|
||||
id={`${p.id}-day`}
|
||||
label="Jour"
|
||||
value={p.value.day}
|
||||
error={isValErr(p.value.day, ServerApi.Config.constraints.date_day)}
|
||||
value={p.value?.day}
|
||||
error={isValErr(p.value?.day, ServerApi.Config.constraints.date_day)}
|
||||
variant="filled"
|
||||
style={{ flex: 20 }}
|
||||
type="number"
|
||||
@ -50,8 +44,8 @@ export function DateInput(p: {
|
||||
const val = Number(e.target.value);
|
||||
p.onValueChange({
|
||||
day: val > 0 ? val : undefined,
|
||||
month: p.value.month,
|
||||
year: p.value.year,
|
||||
month: p.value?.month,
|
||||
year: p.value?.year,
|
||||
});
|
||||
}}
|
||||
inputProps={{
|
||||
@ -64,17 +58,20 @@ export function DateInput(p: {
|
||||
required
|
||||
id={`${p.id}-month`}
|
||||
label="Mois"
|
||||
value={p.value.month}
|
||||
error={isValErr(p.value.month, ServerApi.Config.constraints.date_month)}
|
||||
value={p.value?.month}
|
||||
error={isValErr(
|
||||
p.value?.month,
|
||||
ServerApi.Config.constraints.date_month
|
||||
)}
|
||||
variant="filled"
|
||||
style={{ flex: 20 }}
|
||||
type="number"
|
||||
onChange={(e) => {
|
||||
const val = Number(e.target.value);
|
||||
p.onValueChange({
|
||||
day: p.value.day,
|
||||
day: p.value?.day,
|
||||
month: val > 0 ? val : undefined,
|
||||
year: p.value.year,
|
||||
year: p.value?.year,
|
||||
});
|
||||
}}
|
||||
inputProps={{
|
||||
@ -87,16 +84,16 @@ export function DateInput(p: {
|
||||
required
|
||||
id={`${p.id}-year`}
|
||||
label="Année"
|
||||
value={p.value.year}
|
||||
value={p.value?.year}
|
||||
onChange={(e) => {
|
||||
const val = Number(e.target.value);
|
||||
p.onValueChange({
|
||||
day: p.value.day,
|
||||
month: p.value.month,
|
||||
day: p.value?.day,
|
||||
month: p.value?.month,
|
||||
year: val > 0 ? val : undefined,
|
||||
});
|
||||
}}
|
||||
error={isValErr(p.value.year, ServerApi.Config.constraints.date_year)}
|
||||
error={isValErr(p.value?.year, ServerApi.Config.constraints.date_year)}
|
||||
variant="filled"
|
||||
style={{ flex: 30 }}
|
||||
type="number"
|
||||
|
@ -1,4 +1,25 @@
|
||||
import { Member } from "../../api/MemberApi";
|
||||
import {
|
||||
Autocomplete,
|
||||
Avatar,
|
||||
Box,
|
||||
IconButton,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemButton,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
TextField,
|
||||
Typography,
|
||||
autocompleteClasses,
|
||||
} from "@mui/material";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import { Member, fmtDate } from "../../api/MemberApi";
|
||||
import { useFamily } from "../BaseFamilyRoute";
|
||||
import React from "react";
|
||||
import { MemberPhoto } from "../MemberPhoto";
|
||||
import Icon from "@mdi/react";
|
||||
import { mdiCross } from "@mdi/js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export function MemberInput(p: {
|
||||
editable: boolean;
|
||||
@ -7,5 +28,99 @@ export function MemberInput(p: {
|
||||
label: string;
|
||||
filter: (m: Member) => boolean;
|
||||
}): React.ReactElement {
|
||||
return <>CHOOSE</>;
|
||||
const n = useNavigate();
|
||||
const family = useFamily();
|
||||
|
||||
const choices = family.members.filter(p.filter);
|
||||
|
||||
const [inputValue, setInputValue] = React.useState("");
|
||||
|
||||
if (p.current) {
|
||||
const member = family.members.get(p.current)!;
|
||||
return (
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<Typography variant="body2">{p.label}</Typography>
|
||||
<MemberItem
|
||||
member={member}
|
||||
onClick={
|
||||
!p.editable
|
||||
? () => {
|
||||
n(family.family.URL(`member/${member.id}`));
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
secondary={
|
||||
p.editable ? (
|
||||
<>
|
||||
<IconButton
|
||||
edge="end"
|
||||
onClick={() => p.onValueChange(undefined)}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!p.editable) return <></>;
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
value={p.current ? family.members.get(p.current) : undefined}
|
||||
onChange={(_event: any, newValue: Member | null | undefined) => {
|
||||
p.onValueChange(newValue?.id);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
onInputChange={(_event, newInputValue) => {
|
||||
setInputValue(newInputValue);
|
||||
}}
|
||||
options={choices}
|
||||
sx={{ width: "100%" }}
|
||||
filterOptions={(options, state) =>
|
||||
options.filter((m) =>
|
||||
m?.fullName.toLowerCase().includes(state.inputValue)
|
||||
)
|
||||
}
|
||||
getOptionLabel={(o) => o?.fullName ?? ""}
|
||||
renderInput={(params) => <TextField {...params} label={p.label} />}
|
||||
renderOption={(_props, option, _state) => (
|
||||
<MemberItem
|
||||
member={option}
|
||||
onClick={() => p.onValueChange(option?.id)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function MemberItem(p: {
|
||||
member?: Member;
|
||||
onClick?: () => void;
|
||||
secondary?: React.ReactElement;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<ListItemButton onClick={p.onClick}>
|
||||
<ListItemAvatar>
|
||||
<MemberPhoto member={p.member!} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<>
|
||||
{p.member?.fullName}{" "}
|
||||
{p.member?.dead && <Icon path={mdiCross} size={"1rem"} />}
|
||||
</>
|
||||
}
|
||||
secondary={`${fmtDate(p.member?.dateOfBirth)} - ${fmtDate(
|
||||
p.member?.dateOfDeath
|
||||
)}`}
|
||||
/>
|
||||
{p.secondary && (
|
||||
<ListItemSecondaryAction>{p.secondary}</ListItemSecondaryAction>
|
||||
)}
|
||||
</ListItemButton>
|
||||
);
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
|
||||
import { Area } from "react-easy-crop";
|
||||
import getCroppedImg from "../../utils/crop_image";
|
||||
import UploadIcon from "@mui/icons-material/Upload";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import { isDebug } from "../../utils/debug_utils";
|
||||
|
||||
export function UploadPhotoButton(p: {
|
||||
label: string;
|
||||
@ -55,6 +57,12 @@ export function UploadPhotoButton(p: {
|
||||
}
|
||||
};
|
||||
|
||||
const uploadPhotoFromURL = async () => {
|
||||
const URL = prompt("Image URL ?");
|
||||
if (URL === null || URL.length === 0) return;
|
||||
setImageURL(URL);
|
||||
};
|
||||
|
||||
const cancelCrop = () => {
|
||||
setImageURL(undefined);
|
||||
};
|
||||
@ -90,7 +98,16 @@ export function UploadPhotoButton(p: {
|
||||
>
|
||||
{p.label}
|
||||
</Button>
|
||||
|
||||
{/* Upload button (from URL) */}{" "}
|
||||
{isDebug() && (
|
||||
<Button
|
||||
onClick={uploadPhotoFromURL}
|
||||
variant="outlined"
|
||||
startIcon={<LinkIcon />}
|
||||
>
|
||||
{p.label} from URL
|
||||
</Button>
|
||||
)}{" "}
|
||||
{/* Crop image dialog */}
|
||||
{imageURL && (
|
||||
<ImageCropperDialog
|
||||
|
Reference in New Issue
Block a user