Person page: one-click sex setter (no edit mode)
Setting a person's sex meant clicking Edit, opening a dropdown, and saving. Replace the read-only ♂/♀ symbol next to the name with an always-visible two-button segmented control that PATCHes immediately on click (gender-only; backend PATCH is exclude_unset so the name/other fields are untouched). Clicking the active sex clears it. The full edit form still offers gender for completeness. 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:
@@ -535,6 +535,16 @@ export default function PersonDetailPage() {
|
||||
setEditingPerson(true);
|
||||
}
|
||||
|
||||
// Quick one-click sex setter — no need to open the full edit form. PATCH is
|
||||
// exclude_unset on the backend, so sending only `gender` leaves the rest.
|
||||
async function setGender(value: "male" | "female" | null) {
|
||||
await api.PATCH("/api/v1/trees/{tree_id}/persons/{person_id}", {
|
||||
params: { path: { tree_id: treeId, person_id: personId } },
|
||||
body: { gender: value },
|
||||
});
|
||||
load();
|
||||
}
|
||||
|
||||
async function savePerson() {
|
||||
const { error } = await api.PATCH("/api/v1/trees/{tree_id}/persons/{person_id}", {
|
||||
params: { path: { tree_id: treeId, person_id: personId } },
|
||||
@@ -711,16 +721,35 @@ export default function PersonDetailPage() {
|
||||
<h1 className="flex flex-wrap items-center gap-3 text-3xl font-semibold">
|
||||
<span className="inline-flex items-center gap-2">
|
||||
{person.primary_name ?? "Unnamed person"}
|
||||
{person.gender === "male" && (
|
||||
<span title="Male" aria-label="Male" style={{ color: "rgb(120, 159, 172)" }}>
|
||||
♂
|
||||
</span>
|
||||
)}
|
||||
{person.gender === "female" && (
|
||||
<span title="Female" aria-label="Female" style={{ color: "rgb(196, 138, 146)" }}>
|
||||
♀
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{/* One-click sex setter — no edit mode needed. Active = current; click it again to clear. */}
|
||||
<span className="inline-flex items-center overflow-hidden rounded-md border border-[var(--border)] text-base font-normal">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setGender(person.gender === "male" ? null : "male")}
|
||||
aria-pressed={person.gender === "male"}
|
||||
title={person.gender === "male" ? "Male — click to clear" : "Set male"}
|
||||
className={`px-3 py-1 leading-none transition-colors ${
|
||||
person.gender === "male"
|
||||
? "bg-[rgb(120,159,172)] text-white"
|
||||
: "text-[var(--muted)] hover:bg-bronze/[0.07]"
|
||||
}`}
|
||||
>
|
||||
♂
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setGender(person.gender === "female" ? null : "female")}
|
||||
aria-pressed={person.gender === "female"}
|
||||
title={person.gender === "female" ? "Female — click to clear" : "Set female"}
|
||||
className={`border-l border-[var(--border)] px-3 py-1 leading-none transition-colors ${
|
||||
person.gender === "female"
|
||||
? "bg-[rgb(196,138,146)] text-white"
|
||||
: "text-[var(--muted)] hover:bg-bronze/[0.07]"
|
||||
}`}
|
||||
>
|
||||
♀
|
||||
</button>
|
||||
</span>
|
||||
{isSelf && (
|
||||
<span className="rounded-full bg-bronze/15 px-2.5 py-1 text-xs font-medium text-bronze">
|
||||
|
||||
Reference in New Issue
Block a user