Cleanup: list people with no sex set + inline set #33
@@ -12,6 +12,7 @@ import { Input } from "@/components/ui/input";
|
||||
type Deceased = components["schemas"]["DeceasedCandidate"];
|
||||
type GenderProp = components["schemas"]["GenderProposal"];
|
||||
type NameIssue = components["schemas"]["NameIssue"];
|
||||
type Person = components["schemas"]["PersonRead"];
|
||||
|
||||
const ISSUE_LABEL: Record<string, string> = {
|
||||
date_in_surname: "date in surname",
|
||||
@@ -36,6 +37,9 @@ export default function CleanupPage() {
|
||||
const [genMsg, setGenMsg] = useState<string | null>(null);
|
||||
const genFile = useRef<HTMLInputElement>(null);
|
||||
|
||||
// People still missing a sex (manual mop-up)
|
||||
const [unset, setUnset] = useState<Person[] | null>(null);
|
||||
|
||||
// 3) Name issues
|
||||
const [issues, setIssues] = useState<NameIssue[] | null>(null);
|
||||
const [edits, setEdits] = useState<Record<string, { given: string; surname: string; on: boolean }>>({});
|
||||
@@ -77,6 +81,25 @@ export default function CleanupPage() {
|
||||
setGenSel(new Set(data.map((g) => g.person_id)));
|
||||
}
|
||||
}
|
||||
const loadUnset = useCallback(async () => {
|
||||
const { data } = await api.GET("/api/v1/trees/{tree_id}/persons", {
|
||||
params: { path: { tree_id: treeId } },
|
||||
});
|
||||
setUnset(
|
||||
(data ?? [])
|
||||
.filter((p) => !p.gender)
|
||||
.sort((a, b) => (a.primary_name ?? "").localeCompare(b.primary_name ?? "")),
|
||||
);
|
||||
}, [treeId]);
|
||||
|
||||
async function setSex(personId: string, gender: "male" | "female") {
|
||||
await api.PATCH("/api/v1/trees/{tree_id}/persons/{person_id}", {
|
||||
params: { path: { tree_id: treeId, person_id: personId } },
|
||||
body: { gender },
|
||||
});
|
||||
setUnset((prev) => (prev ? prev.filter((p) => p.id !== personId) : prev));
|
||||
}
|
||||
|
||||
async function guessGender() {
|
||||
setGenMsg(null);
|
||||
const { data } = await api.GET("/api/v1/trees/{tree_id}/cleanup/gender/guess", {
|
||||
@@ -96,6 +119,7 @@ export default function CleanupPage() {
|
||||
});
|
||||
setGenMsg(`Set gender on ${data?.updated ?? 0} people.`);
|
||||
setGender(null);
|
||||
loadUnset();
|
||||
}
|
||||
|
||||
const loadNames = useCallback(async () => {
|
||||
@@ -113,7 +137,8 @@ export default function CleanupPage() {
|
||||
|
||||
useEffect(() => {
|
||||
loadNames();
|
||||
}, [loadNames]);
|
||||
loadUnset();
|
||||
}, [loadNames, loadUnset]);
|
||||
|
||||
async function applyNames() {
|
||||
const chosen = (issues ?? []).filter((i) => edits[i.name_id]?.on);
|
||||
@@ -261,6 +286,49 @@ export default function CleanupPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* People still missing a sex */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">
|
||||
People with no sex set{unset ? ` (${unset.length})` : ""}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{unset === null ? (
|
||||
<p className="text-sm text-[var(--muted)]">Loading…</p>
|
||||
) : unset.length === 0 ? (
|
||||
<p className="text-sm text-[var(--muted)]">Everyone has a sex set. 🎉</p>
|
||||
) : (
|
||||
<ul className="max-h-80 divide-y divide-[var(--border)] overflow-auto rounded-lg border border-[var(--border)]">
|
||||
{unset.map((p) => (
|
||||
<li key={p.id} className="flex items-center gap-3 px-3 py-1.5 text-sm">
|
||||
<a
|
||||
href={`/trees/${treeId}/persons/${p.id}`}
|
||||
className="flex-1 truncate hover:underline"
|
||||
>
|
||||
{p.primary_name ?? "Unnamed"}
|
||||
</a>
|
||||
<button
|
||||
onClick={() => setSex(p.id, "male")}
|
||||
className="rounded px-2 py-0.5 text-xs"
|
||||
style={{ color: "rgb(120,159,172)", border: "1px solid rgb(120,159,172)" }}
|
||||
>
|
||||
♂ Male
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSex(p.id, "female")}
|
||||
className="rounded px-2 py-0.5 text-xs"
|
||||
style={{ color: "rgb(196,138,146)", border: "1px solid rgb(196,138,146)" }}
|
||||
>
|
||||
♀ Female
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 3) Name issues */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
Reference in New Issue
Block a user