}
diff --git a/geneit_app/src/utils/debug_utils.ts b/geneit_app/src/utils/debug_utils.ts
new file mode 100644
index 0000000..d3b8c4f
--- /dev/null
+++ b/geneit_app/src/utils/debug_utils.ts
@@ -0,0 +1,3 @@
+export function isDebug(): boolean {
+ return !process.env.NODE_ENV || process.env.NODE_ENV === "development";
+}
diff --git a/geneit_app/src/widgets/MemberPhoto.tsx b/geneit_app/src/widgets/MemberPhoto.tsx
index 1213b91..a0de049 100644
--- a/geneit_app/src/widgets/MemberPhoto.tsx
+++ b/geneit_app/src/widgets/MemberPhoto.tsx
@@ -3,13 +3,17 @@ import { Member } from "../api/MemberApi";
export function MemberPhoto(p: {
member: Member;
- width: number;
+ width?: number;
}): React.ReactElement {
return (
);
}
diff --git a/geneit_app/src/widgets/forms/DateInput.tsx b/geneit_app/src/widgets/forms/DateInput.tsx
index f576f7e..ec2686d 100644
--- a/geneit_app/src/widgets/forms/DateInput.tsx
+++ b/geneit_app/src/widgets/forms/DateInput.tsx
@@ -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 (
- {p.label} : {p.value.day ?? "__"} / {p.value.month ?? "__"} /{" "}
- {p.value.year ?? "__"}
+ {p.label} : {fmtDate(p.value!)}
);
}
@@ -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"
diff --git a/geneit_app/src/widgets/forms/MemberInput.tsx b/geneit_app/src/widgets/forms/MemberInput.tsx
index cb9a8d8..3d9cda3 100644
--- a/geneit_app/src/widgets/forms/MemberInput.tsx
+++ b/geneit_app/src/widgets/forms/MemberInput.tsx
@@ -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 (
+
+ {p.label}
+ {
+ n(family.family.URL(`member/${member.id}`));
+ }
+ : undefined
+ }
+ secondary={
+ p.editable ? (
+ <>
+ p.onValueChange(undefined)}
+ >
+
+
+ >
+ ) : undefined
+ }
+ />
+
+ );
+ }
+
+ if (!p.editable) return <>>;
+
+ return (
+ {
+ 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) => }
+ renderOption={(_props, option, _state) => (
+ p.onValueChange(option?.id)}
+ />
+ )}
+ />
+ );
+}
+
+function MemberItem(p: {
+ member?: Member;
+ onClick?: () => void;
+ secondary?: React.ReactElement;
+}): React.ReactElement {
+ return (
+
+
+
+
+
+ {p.member?.fullName}{" "}
+ {p.member?.dead && }
+ >
+ }
+ secondary={`${fmtDate(p.member?.dateOfBirth)} - ${fmtDate(
+ p.member?.dateOfDeath
+ )}`}
+ />
+ {p.secondary && (
+ {p.secondary}
+ )}
+
+ );
}
diff --git a/geneit_app/src/widgets/forms/UploadPhotoButton.tsx b/geneit_app/src/widgets/forms/UploadPhotoButton.tsx
index 953353c..4ae35cd 100644
--- a/geneit_app/src/widgets/forms/UploadPhotoButton.tsx
+++ b/geneit_app/src/widgets/forms/UploadPhotoButton.tsx
@@ -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}
-
+ {/* Upload button (from URL) */}{" "}
+ {isDebug() && (
+ }
+ >
+ {p.label} from URL
+
+ )}{" "}
{/* Crop image dialog */}
{imageURL && (