Edit UI for people and life events; existing-person picker in family view
Person detail: an Edit form for name + gender + living status + privacy, and inline edit of each life event (type + structured date). Family view: the add-relative buttons now search existing people (link the real person) or create new — preventing duplicate spouses/parents — and adding a child to someone with one spouse links both parents. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
@@ -122,23 +122,42 @@ export default function FamilyViewPage() {
|
||||
load();
|
||||
}
|
||||
|
||||
async function submitAdd(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!adding || !addName.trim()) return;
|
||||
const newId = await addPerson(addName);
|
||||
if (newId) {
|
||||
const { kind, anchor } = adding;
|
||||
const body =
|
||||
kind === "parent"
|
||||
? { type: "parent_child" as const, person_from_id: newId, person_to_id: anchor, qualifier: "biological" as const }
|
||||
: kind === "child"
|
||||
? { type: "parent_child" as const, person_from_id: anchor, person_to_id: newId, qualifier: "biological" as const }
|
||||
: { type: "partnership" as const, person_from_id: anchor, person_to_id: newId };
|
||||
async function postRel(body: components["schemas"]["RelationshipCreate"]) {
|
||||
await api.POST("/api/v1/trees/{tree_id}/relationships", {
|
||||
params: { path: { tree_id: treeId } },
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
// Create the relationship(s) connecting an (existing or new) person to anchor.
|
||||
async function createLink(kind: AddKind, anchor: string, personId: string) {
|
||||
if (kind === "parent") {
|
||||
await postRel({ type: "parent_child", person_from_id: personId, person_to_id: anchor, qualifier: "biological" });
|
||||
} else if (kind === "partner") {
|
||||
await postRel({ type: "partnership", person_from_id: anchor, person_to_id: personId });
|
||||
} else {
|
||||
// child: link to anchor, and to anchor's spouse too (so both parents show)
|
||||
await postRel({ type: "parent_child", person_from_id: anchor, person_to_id: personId, qualifier: "biological" });
|
||||
const partners = partnersOf(anchor);
|
||||
if (partners.length === 1) {
|
||||
await postRel({ type: "parent_child", person_from_id: partners[0], person_to_id: personId, qualifier: "biological" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function linkExisting(personId: string) {
|
||||
if (!adding) return;
|
||||
await createLink(adding.kind, adding.anchor, personId);
|
||||
setAdding(null);
|
||||
setAddName("");
|
||||
load();
|
||||
}
|
||||
|
||||
async function submitAdd(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!adding || !addName.trim()) return;
|
||||
const newId = await addPerson(addName);
|
||||
if (newId) await createLink(adding.kind, adding.anchor, newId);
|
||||
setAdding(null);
|
||||
setAddName("");
|
||||
load();
|
||||
@@ -210,26 +229,45 @@ export default function FamilyViewPage() {
|
||||
label: string;
|
||||
}) =>
|
||||
adding?.key === formKey ? (
|
||||
<form onSubmit={submitAdd} className="flex w-44 flex-col gap-1">
|
||||
<form onSubmit={submitAdd} className="flex w-56 flex-col gap-1">
|
||||
<Input
|
||||
autoFocus
|
||||
className="h-9"
|
||||
placeholder="Full name"
|
||||
placeholder="Search existing or type a new name"
|
||||
value={addName}
|
||||
onChange={(e) => setAddName(e.target.value)}
|
||||
/>
|
||||
<div className="flex gap-1">
|
||||
<Button type="submit" size="sm">
|
||||
Add
|
||||
</Button>
|
||||
{addName.trim() && (
|
||||
<div className="overflow-hidden rounded-md border border-[var(--border)] bg-[var(--surface)] text-sm">
|
||||
{people
|
||||
.filter(
|
||||
(p) =>
|
||||
p.id !== anchor &&
|
||||
(p.primary_name ?? "").toLowerCase().includes(addName.trim().toLowerCase()),
|
||||
)
|
||||
.slice(0, 6)
|
||||
.map((p) => (
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => setAdding(null)}
|
||||
className="text-xs text-[var(--muted)]"
|
||||
onClick={() => linkExisting(p.id)}
|
||||
className="flex w-full items-center justify-between gap-2 px-2 py-1.5 text-left hover:bg-bronze/[0.07]"
|
||||
>
|
||||
cancel
|
||||
<span className="truncate">{p.primary_name ?? "Unnamed"}</span>
|
||||
<span className="shrink-0 text-xs text-[var(--muted)]">{years.get(p.id) ?? ""}</span>
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
type="submit"
|
||||
className="flex w-full items-center gap-1 border-t border-[var(--border)] px-2 py-1.5 text-left text-bronze hover:bg-bronze/[0.07]"
|
||||
>
|
||||
+ Create new “{addName.trim()}”
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button type="button" onClick={() => setAdding(null)} className="text-xs text-[var(--muted)]">
|
||||
cancel
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<button
|
||||
|
||||
@@ -33,6 +33,53 @@ const GED_MON = ["", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SE
|
||||
const DATE_QUALS: Record<string, string> = { exact: "", about: "ABT", before: "BEF", after: "AFT" };
|
||||
const pad = (n: number, len: number) => String(n).padStart(len, "0");
|
||||
|
||||
function composeDate(qual: string, day: string, month: string, year: string) {
|
||||
const y = year.trim();
|
||||
if (!y || Number.isNaN(Number(y))) {
|
||||
return { date_value: null as string | null, date_start: null as string | null, date_precision: null as string | null };
|
||||
}
|
||||
const m = month ? Number(month) : null;
|
||||
const d = day.trim() ? Number(day) : null;
|
||||
const parts: string[] = [];
|
||||
if (d && m) parts.push(String(d));
|
||||
if (m) parts.push(GED_MON[m]);
|
||||
parts.push(y);
|
||||
const prefix = DATE_QUALS[qual];
|
||||
return {
|
||||
date_value: (prefix ? `${prefix} ` : "") + parts.join(" "),
|
||||
date_start: `${pad(Number(y), 4)}-${pad(m ?? 1, 2)}-${pad(d ?? 1, 2)}`,
|
||||
date_precision: qual,
|
||||
};
|
||||
}
|
||||
|
||||
// Parse a stored date_value (e.g. "ABT 12 MAR 1900") back into form fields.
|
||||
function parseDateValue(v: string | null | undefined) {
|
||||
let qual = "exact";
|
||||
let day = "";
|
||||
let month = "";
|
||||
let year = "";
|
||||
if (v) {
|
||||
let s = v.trim();
|
||||
const up = s.toUpperCase();
|
||||
for (const [q, pre] of Object.entries(DATE_QUALS)) {
|
||||
if (pre && up.startsWith(`${pre} `)) {
|
||||
qual = q;
|
||||
s = s.slice(pre.length + 1).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const t of s.toUpperCase().split(/\s+/).filter(Boolean)) {
|
||||
if (/^\d{3,4}$/.test(t) && !year) year = t;
|
||||
else if (/^\d{1,2}$/.test(t)) day = String(Number(t));
|
||||
else {
|
||||
const mi = GED_MON.indexOf(t);
|
||||
if (mi > 0) month = String(mi);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { qual, day, month, year };
|
||||
}
|
||||
|
||||
export default function PersonDetailPage() {
|
||||
const router = useRouter();
|
||||
const params = useParams<{ id: string; personId: string }>();
|
||||
@@ -54,6 +101,23 @@ export default function PersonDetailPage() {
|
||||
const [dateMonth, setDateMonth] = useState("");
|
||||
const [dateYear, setDateYear] = useState("");
|
||||
|
||||
// Inline edit-event form.
|
||||
const [editId, setEditId] = useState<string | null>(null);
|
||||
const [edType, setEdType] = useState("birth");
|
||||
const [edTypeOther, setEdTypeOther] = useState("");
|
||||
const [edQual, setEdQual] = useState("exact");
|
||||
const [edDay, setEdDay] = useState("");
|
||||
const [edMonth, setEdMonth] = useState("");
|
||||
const [edYear, setEdYear] = useState("");
|
||||
|
||||
// Inline edit-person form (name + vitals).
|
||||
const [editingPerson, setEditingPerson] = useState(false);
|
||||
const [pGiven, setPGiven] = useState("");
|
||||
const [pSurname, setPSurname] = useState("");
|
||||
const [pGender, setPGender] = useState("");
|
||||
const [pLiving, setPLiving] = useState("unknown");
|
||||
const [pPrivacy, setPPrivacy] = useState<"inherit" | "private" | "public">("inherit");
|
||||
|
||||
const [relKind, setRelKind] = useState<"parent" | "child" | "partner" | "sibling">("parent");
|
||||
const [relOther, setRelOther] = useState("");
|
||||
const [relQual, setRelQual] = useState<Qualifier>("biological");
|
||||
@@ -112,30 +176,16 @@ export default function PersonDetailPage() {
|
||||
const eventCites = (id: string) => citations.filter((c) => c.event_id === id);
|
||||
const personCites = citations.filter((c) => c.person_id === personId);
|
||||
|
||||
function buildDate() {
|
||||
const year = dateYear.trim();
|
||||
if (!year || Number.isNaN(Number(year))) {
|
||||
return { date_value: null, date_start: null, date_precision: null };
|
||||
}
|
||||
const m = dateMonth ? Number(dateMonth) : null;
|
||||
const d = dateDay.trim() ? Number(dateDay) : null;
|
||||
const parts: string[] = [];
|
||||
if (d && m) parts.push(String(d));
|
||||
if (m) parts.push(GED_MON[m]);
|
||||
parts.push(year);
|
||||
const prefix = DATE_QUALS[dateQual];
|
||||
return {
|
||||
date_value: (prefix ? `${prefix} ` : "") + parts.join(" "),
|
||||
date_start: `${pad(Number(year), 4)}-${pad(m ?? 1, 2)}-${pad(d ?? 1, 2)}`,
|
||||
date_precision: dateQual,
|
||||
};
|
||||
}
|
||||
|
||||
async function addEvent(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
const event_type = evType === "other" ? evTypeOther.trim() : evType;
|
||||
if (!event_type) return;
|
||||
const { date_value, date_start, date_precision } = buildDate();
|
||||
const { date_value, date_start, date_precision } = composeDate(
|
||||
dateQual,
|
||||
dateDay,
|
||||
dateMonth,
|
||||
dateYear,
|
||||
);
|
||||
const { error } = await api.POST("/api/v1/trees/{tree_id}/events", {
|
||||
params: { path: { tree_id: treeId } },
|
||||
body: { event_type, person_id: personId, date_value, date_start, date_precision },
|
||||
@@ -156,6 +206,33 @@ export default function PersonDetailPage() {
|
||||
load();
|
||||
}
|
||||
|
||||
function startEdit(ev: Event) {
|
||||
setEditId(ev.id);
|
||||
const known = EVENT_TYPES.includes(ev.event_type);
|
||||
setEdType(known ? ev.event_type : "other");
|
||||
setEdTypeOther(known ? "" : ev.event_type);
|
||||
const parsed = parseDateValue(ev.date_value);
|
||||
setEdQual(parsed.qual);
|
||||
setEdDay(parsed.day);
|
||||
setEdMonth(parsed.month);
|
||||
setEdYear(parsed.year);
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
if (!editId) return;
|
||||
const event_type = edType === "other" ? edTypeOther.trim() : edType;
|
||||
if (!event_type) return;
|
||||
const { date_value, date_start, date_precision } = composeDate(edQual, edDay, edMonth, edYear);
|
||||
const { error } = await api.PATCH("/api/v1/trees/{tree_id}/events/{event_id}", {
|
||||
params: { path: { tree_id: treeId, event_id: editId } },
|
||||
body: { event_type, date_value, date_start, date_precision },
|
||||
});
|
||||
if (!error) {
|
||||
setEditId(null);
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
async function addRel(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!relOther) return;
|
||||
@@ -213,6 +290,33 @@ export default function PersonDetailPage() {
|
||||
router.push(`/trees/${treeId}`);
|
||||
}
|
||||
|
||||
function startEditPerson(current: Person) {
|
||||
const t = (current.primary_name ?? "").trim().split(/\s+/).filter(Boolean);
|
||||
setPGiven(t.length > 1 ? t.slice(0, -1).join(" ") : (t[0] ?? ""));
|
||||
setPSurname(t.length > 1 ? t[t.length - 1] : "");
|
||||
setPGender(current.gender ?? "");
|
||||
setPLiving(current.is_living === true ? "living" : current.is_living === false ? "deceased" : "unknown");
|
||||
setPPrivacy((current.privacy as "inherit" | "private" | "public") ?? "inherit");
|
||||
setEditingPerson(true);
|
||||
}
|
||||
|
||||
async function savePerson() {
|
||||
const { error } = await api.PATCH("/api/v1/trees/{tree_id}/persons/{person_id}", {
|
||||
params: { path: { tree_id: treeId, person_id: personId } },
|
||||
body: {
|
||||
given: pGiven || null,
|
||||
surname: pSurname || null,
|
||||
gender: pGender || null,
|
||||
is_living: pLiving === "living" ? true : pLiving === "deceased" ? false : null,
|
||||
privacy: pPrivacy,
|
||||
},
|
||||
});
|
||||
if (!error) {
|
||||
setEditingPerson(false);
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
if (!ready) return <p className="text-[var(--muted)]">Loading…</p>;
|
||||
if (!person) return <p className="text-[var(--muted)]">Not found.</p>;
|
||||
|
||||
@@ -316,15 +420,56 @@ export default function PersonDetailPage() {
|
||||
← Back to tree
|
||||
</Link>
|
||||
|
||||
{editingPerson ? (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
savePerson();
|
||||
}}
|
||||
className="space-y-3 rounded-lg border border-[var(--border)] p-4"
|
||||
>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Input className="w-40" placeholder="Given name" value={pGiven} onChange={(e) => setPGiven(e.target.value)} />
|
||||
<Input className="w-40" placeholder="Surname" value={pSurname} onChange={(e) => setPSurname(e.target.value)} />
|
||||
<Input className="w-32" placeholder="Gender" value={pGender} onChange={(e) => setPGender(e.target.value)} />
|
||||
<select className={fieldCls} value={pLiving} onChange={(e) => setPLiving(e.target.value)}>
|
||||
<option value="unknown">Status: unknown</option>
|
||||
<option value="living">Living</option>
|
||||
<option value="deceased">Deceased</option>
|
||||
</select>
|
||||
<select
|
||||
className={fieldCls}
|
||||
value={pPrivacy}
|
||||
onChange={(e) => setPPrivacy(e.target.value as "inherit" | "private" | "public")}
|
||||
>
|
||||
<option value="inherit">Privacy: default</option>
|
||||
<option value="private">Private</option>
|
||||
<option value="public">Public</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" size="sm">
|
||||
Save
|
||||
</Button>
|
||||
<button type="button" onClick={() => setEditingPerson(false)} className="text-xs text-[var(--muted)]">
|
||||
cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<h1 className="text-3xl font-semibold">{person.primary_name ?? "Unnamed person"}</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
{citeControl("p", { person_id: personId }, personCites)}
|
||||
<Button variant="outline" size="sm" onClick={() => startEditPerson(person)}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={removePerson}>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -335,7 +480,72 @@ export default function PersonDetailPage() {
|
||||
<p className="text-sm text-[var(--muted)]">No events yet.</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{events.map((ev) => (
|
||||
{events.map((ev) =>
|
||||
editId === ev.id ? (
|
||||
<li key={ev.id}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
saveEdit();
|
||||
}}
|
||||
className="flex flex-wrap items-end gap-2"
|
||||
>
|
||||
<select
|
||||
className={`${fieldCls} capitalize`}
|
||||
value={edType}
|
||||
onChange={(e) => setEdType(e.target.value)}
|
||||
>
|
||||
{EVENT_TYPES.map((t) => (
|
||||
<option key={t} value={t} className="capitalize">
|
||||
{t}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{edType === "other" && (
|
||||
<Input
|
||||
className="h-9 w-32"
|
||||
placeholder="Custom"
|
||||
value={edTypeOther}
|
||||
onChange={(e) => setEdTypeOther(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
<select className={fieldCls} value={edQual} onChange={(e) => setEdQual(e.target.value)}>
|
||||
<option value="exact">on</option>
|
||||
<option value="about">about</option>
|
||||
<option value="before">before</option>
|
||||
<option value="after">after</option>
|
||||
</select>
|
||||
<input
|
||||
className={`${fieldCls} w-14`}
|
||||
inputMode="numeric"
|
||||
placeholder="Day"
|
||||
value={edDay}
|
||||
onChange={(e) => setEdDay(e.target.value)}
|
||||
/>
|
||||
<select className={fieldCls} value={edMonth} onChange={(e) => setEdMonth(e.target.value)}>
|
||||
<option value="">—</option>
|
||||
{MONTHS.map((m, i) => (i > 0 ? <option key={i} value={i}>{m}</option> : null))}
|
||||
</select>
|
||||
<input
|
||||
className={`${fieldCls} w-20`}
|
||||
inputMode="numeric"
|
||||
placeholder="Year"
|
||||
value={edYear}
|
||||
onChange={(e) => setEdYear(e.target.value)}
|
||||
/>
|
||||
<Button type="submit" size="sm">
|
||||
Save
|
||||
</Button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditId(null)}
|
||||
className="text-xs text-[var(--muted)]"
|
||||
>
|
||||
cancel
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
) : (
|
||||
<li key={ev.id} className="flex flex-wrap items-center justify-between gap-2 text-sm">
|
||||
<span>
|
||||
<span className="font-medium capitalize">{ev.event_type}</span>
|
||||
@@ -345,6 +555,12 @@ export default function PersonDetailPage() {
|
||||
</span>
|
||||
<span className="flex items-center gap-3">
|
||||
{citeControl(`e:${ev.id}`, { event_id: ev.id }, eventCites(ev.id))}
|
||||
<button
|
||||
onClick={() => startEdit(ev)}
|
||||
className="text-xs text-bronze hover:underline"
|
||||
>
|
||||
edit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => removeEvent(ev.id)}
|
||||
className="text-[var(--muted)] hover:text-bronze"
|
||||
@@ -354,7 +570,8 @@ export default function PersonDetailPage() {
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
<form onSubmit={addEvent} className="flex flex-wrap items-end gap-2">
|
||||
|
||||
Vendored
+111
-2
@@ -243,7 +243,8 @@ export interface paths {
|
||||
delete: operations["delete_person_api_v1_trees__tree_id__persons__person_id__delete"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
/** Update Person */
|
||||
patch: operations["update_person_api_v1_trees__tree_id__persons__person_id__patch"];
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/persons/{person_id}/restore": {
|
||||
@@ -312,7 +313,8 @@ export interface paths {
|
||||
delete: operations["delete_event_api_v1_trees__tree_id__events__event_id__delete"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
/** Update Event */
|
||||
patch: operations["update_event_api_v1_trees__tree_id__events__event_id__patch"];
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/trees/{tree_id}/relationships": {
|
||||
@@ -676,6 +678,27 @@ export interface components {
|
||||
*/
|
||||
created_at: string;
|
||||
};
|
||||
/** EventUpdate */
|
||||
EventUpdate: {
|
||||
/** Event Type */
|
||||
event_type?: string | null;
|
||||
/** Place Id */
|
||||
place_id?: string | null;
|
||||
/** Date Value */
|
||||
date_value?: string | null;
|
||||
/** Date Start */
|
||||
date_start?: string | null;
|
||||
/** Date End */
|
||||
date_end?: string | null;
|
||||
/** Date Precision */
|
||||
date_precision?: string | null;
|
||||
/** Calendar */
|
||||
calendar?: string | null;
|
||||
/** Detail */
|
||||
detail?: string | null;
|
||||
/** Notes */
|
||||
notes?: string | null;
|
||||
};
|
||||
/** HTTPValidationError */
|
||||
HTTPValidationError: {
|
||||
/** Detail */
|
||||
@@ -798,6 +821,20 @@ export interface components {
|
||||
*/
|
||||
created_at: string;
|
||||
};
|
||||
/** PersonUpdate */
|
||||
PersonUpdate: {
|
||||
/** Given */
|
||||
given?: string | null;
|
||||
/** Surname */
|
||||
surname?: string | null;
|
||||
/** Gender */
|
||||
gender?: string | null;
|
||||
/** Is Living */
|
||||
is_living?: boolean | null;
|
||||
privacy?: components["schemas"]["PersonPrivacy"] | null;
|
||||
/** Notes */
|
||||
notes?: string | null;
|
||||
};
|
||||
/** RegisterRequest */
|
||||
RegisterRequest: {
|
||||
/** Email */
|
||||
@@ -1539,6 +1576,42 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
update_person_api_v1_trees__tree_id__persons__person_id__patch: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
person_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["PersonUpdate"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PersonRead"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
restore_person_api_v1_trees__tree_id__persons__person_id__restore_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -1699,6 +1772,42 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
update_event_api_v1_trees__tree_id__events__event_id__patch: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
tree_id: string;
|
||||
event_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["EventUpdate"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["EventRead"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
list_relationships_api_v1_trees__tree_id__relationships_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
@@ -611,6 +611,67 @@
|
||||
}
|
||||
},
|
||||
"/api/v1/trees/{tree_id}/persons/{person_id}": {
|
||||
"patch": {
|
||||
"tags": [
|
||||
"persons"
|
||||
],
|
||||
"summary": "Update Person",
|
||||
"operationId": "update_person_api_v1_trees__tree_id__persons__person_id__patch",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "person_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Person Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PersonUpdate"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PersonRead"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"persons"
|
||||
@@ -916,6 +977,67 @@
|
||||
}
|
||||
},
|
||||
"/api/v1/trees/{tree_id}/events/{event_id}": {
|
||||
"patch": {
|
||||
"tags": [
|
||||
"events"
|
||||
],
|
||||
"summary": "Update Event",
|
||||
"operationId": "update_event_api_v1_trees__tree_id__events__event_id__patch",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tree_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Tree Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "event_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Event Id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/EventUpdate"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/EventRead"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"events"
|
||||
@@ -2361,6 +2483,114 @@
|
||||
],
|
||||
"title": "EventRead"
|
||||
},
|
||||
"EventUpdate": {
|
||||
"properties": {
|
||||
"event_type": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Event Type"
|
||||
},
|
||||
"place_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Place Id"
|
||||
},
|
||||
"date_value": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Date Value"
|
||||
},
|
||||
"date_start": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Date Start"
|
||||
},
|
||||
"date_end": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Date End"
|
||||
},
|
||||
"date_precision": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Date Precision"
|
||||
},
|
||||
"calendar": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Calendar"
|
||||
},
|
||||
"detail": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Detail"
|
||||
},
|
||||
"notes": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Notes"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "EventUpdate"
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
@@ -2709,6 +2939,77 @@
|
||||
],
|
||||
"title": "PersonRead"
|
||||
},
|
||||
"PersonUpdate": {
|
||||
"properties": {
|
||||
"given": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Given"
|
||||
},
|
||||
"surname": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Surname"
|
||||
},
|
||||
"gender": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Gender"
|
||||
},
|
||||
"is_living": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Is Living"
|
||||
},
|
||||
"privacy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/PersonPrivacy"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Notes"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "PersonUpdate"
|
||||
},
|
||||
"RegisterRequest": {
|
||||
"properties": {
|
||||
"email": {
|
||||
|
||||
Reference in New Issue
Block a user